RSS

Monthly Archives: April 2011

Add icons to NestedList in Sencha Touch

NestedList with icons

Unlike most other components NestedLists is missing itemTpl property.

According to Sencha documentation we should use the method called getItemTextTpl instead.

The quick example. Assuming your store looks something like this:

var eventsListStore = new Ext.data.TreeStore({
    model: 'EventItem',
    root: {
        items: [
            {leaf: true, eventName: 'Event 1', eventImage: 'http://url to image'},
            {leaf: true, eventName: 'Event 2', eventImage: 'http://url to image 2'},
            ...
       ]
    },
    proxy: {
        type: 'ajax',
        reader: {
            type: 'tree',
            root: 'items'
        }
    }
});

Then NestedList configuration would be:

...
store: eventsListStore,
getItemTextTpl: function() {
    return '<img class="eventIcon" src="{eventImage}"> {eventName}';
}
...
 
2 Comments

Posted by on April 29, 2011 in misc

 

Auto height and window shadow

Incorrect window shadow

The issue seems to appear when window autoHeight property is set to true. Which means that window height will be adjusted according to window contents height.But it starts misbehaving if you dynamically change the window contents – resize or add/delete new items.

Looks like the easiest solution is to call syncShadow() method on your window object.

    Ext.getCmp('myWindow').syncShadow();

The best place to stick it is after you modified the child controls or on window resize event.

* I haven’t tried replicating this issue in ExtJS 4.0.0 but it appeared on my 3.3.0 and prior.

 
Leave a comment

Posted by on April 28, 2011 in Sencha

 

Tags: , , ,

EditorGridPanel not firing afteredit event if value didn’t change

EditorGridPanel

If you are using EditorGridPanel plugin for ExtJS grids chances are you want to know when user starts and finishes editing a cell.

There is no problem to find out when edit starts with beforeedit event. But afteredit event ExtJS provides for capturing edit completion only fires if the content of the cell has changed. But if the user leaves the cell without modifying it there is no way of capturing this with afteredit.

But we can always override ExtJS internal methods to achieve required functionality ;). In this case we extend EditorGridPanel’s onEditComplete function:

Ext.grid.EditorGridPanel.prototype.onEditComplete = function(ed, value, startValue)
{
        this.editing = false;
        this.lastActiveEditor = this.activeEditor;
        this.activeEditor = null;

        var r = ed.record,
            field = this.colModel.getDataIndex(ed.col);
        value = this.postEditValue(value, startValue, r, field);
        if(this.forceValidation === true || String(value) !== String(startValue)){
            var e = {
                grid: this,
                record: r,
                field: field,
                originalValue: startValue,
                value: value,
                row: ed.row,
                column: ed.col,
                cancel:false
            };
            if(this.fireEvent("validateedit", e) !== false && !e.cancel /* && String(value) !== String(startValue) */){
                r.set(field, e.value);
                delete e.cancel;
                this.fireEvent("afteredit", e);
            }
        }
        this.view.focusCell(ed.row, ed.col);
};  
 
Leave a comment

Posted by on April 15, 2011 in misc, Sencha

 

Tags: , ,

SVG image zooming and panning with RaphaelJs


raphaeljs image zoom

View the demo here.

Easy way to do image zooming and panning without sending images to the server to be processed and trimmed with RaphaelJS and a few simple steps.

1. Set initial zoom, image window width and height:

var mapZoom = 1;

var mapWidth = 400;
var mapHeight = 400;
$('#mapHolder').css('width', mapWidth);
$('#mapHolder').css('height', mapHeight);

2. Create a RaphaelJS object and add a full-size image to it.

var paper = Raphael("mapHolder", mapWidth, mapHeight);
var mapImage = paper.image("https://dashasalo.files.wordpress.com/2011/09/figure1-worldatlas-large.jpg", 0, 0, mapWidth, mapHeight);

You should see your image resized to specified mapWidth and mapHeight.

3. Next step is to draw a menu of controls. I used RaphaelJS to draw the controls but you can also use images if you like.

var mapMenuHolderWidth = 40;
var mapMenuHolderHeight = 216;
var mapMenuHolderX = mapWidth - mapMenuHolderWidth - 6; // menu right margin
var mapMenuHolderY = 5; // menu top margin

// draw menu panel
var mapMenuHolder = paper.rect(mapMenuHolderX, mapMenuHolderY, mapMenuHolderWidth, mapMenuHolderHeight, 12).attr({fill: 'black', stroke: '#aaa', 'stroke-width': 2, opacity: 0.7});

Now controls themselves. You can find very nice icons and paths used to draw them on RaphaelJs website. SVG paths for icons were quite long and made code unreadable so I removed them from this code snippet. If you need paths have a look at the demo source code.

var maxIcon = paper.path("M22...051z").attr({fill: "#bbb", stroke: "#000", translation: (mapMenuHolderX+5) + ', '+(mapMenuHolderY + 10)});

var minIcon = paper.path("M22...884z").attr({fill: "#bbb", stroke: "#000", translation: (mapMenuHolderX+5) + ', '+(mapMenuHolderY + 7+ 34)});
  		
var arrowTop = paper.path("M239...73z").attr({fill: "#bbb", stroke: "#000", scale: 0.10, translation: '245, -25', rotation: 180});
  		
var arrowLeft = paper.path("M239...73z").attr({fill: "#bbb", stroke: "#000", scale: 0.10, translation: '245, 4', rotation: 90});
  		
var arrowRight = paper.path("M239...73z").attr({fill: "#bbb", stroke: "#000", scale: 0.10, translation: '245, 33', rotation: -90});
  		
var arrowBottom = paper.path("M239...73z").attr({fill: "#bbb", stroke: "#000", scale: 0.10, translation: '245, 63'});

I used one path for all the arrows and just rotated it to make it point different direction.

4. Now we add a bunch of handlers to the controls to catch the click event.

// maximize icon clicked 
maxIcon.click(function(e){
  	mapZoom++;
  	mapImage.scale(mapZoom, mapZoom);
  	e.stopPropagation();
  	e.preventDefault();
  }); 

// minimize icon clicked 
  minIcon.click(function(e){
  	if (mapZoom == 1) return;
  	mapZoom--;
  	mapImage.scale(mapZoom, mapZoom);
  	adjustMapEdge();
  	e.stopPropagation();
  	e.preventDefault();
  }); 
  
// top arrow clicked		
arrowTop.click(function(e){
  	mapImage.translate(0, 20);
  	adjustMapEdge();
  	e.stopPropagation();
  	e.preventDefault();
}); 
  
// bottom arrow clicked		
arrowBottom.click(function(e){
  	mapImage.translate(0, -20);
  	adjustMapEdge();
  	e.stopPropagation();
  	e.preventDefault();
}); 
  
// left arrow clicked		
arrowLeft.click(function(e){
  	mapImage.translate(20, 0);
  	adjustMapEdge();
  	e.stopPropagation();
  	e.preventDefault();
}); 
  		
// right arrow clicked
arrowRight.click(function(e){
  	mapImage.translate(-20, 0);
  	adjustMapEdge();
  	e.stopPropagation();
  	e.preventDefault();
});  	

We will have a look at adjustMapEdge function in a second.

You can also add nice hover effects to the controls with mouseover and mousedown events (look at my demo).

5. AdjustMapEdge function makes sure that image doesn’t move out of its window boundaries. For instance, if user moves up for long enough eventually he/she will hit the image top edge. When this happens we shouldn’t allow the image move any further up and just use its current location even if the user drags the image up again. This way from this position the user can only move down, left or right.

var adjustMapEdge = function()
{
    if (mapImage.attr('x') > 0) mapImage.attr({'x': 0});
    if (mapImage.attr('x') < (mapWidth - mapImage.attr('width'))) mapImage.attr({'x': (mapWidth - mapImage.attr('width'))});
    		
    if (mapImage.attr('y') > 0) mapImage.attr({'y': 0});
    if (mapImage.attr('y') < (mapHeight - mapImage.attr('height') )) mapImage.attr({'y': (mapHeight - mapImage.attr('height') )});
};

6. Panning the image. Panning the image can be split into 3 basic steps – grab the image, hold the mouse button and drag the image, release the mouse button and drop the image.

As there is no reliable way to say whether mouse button is being hold when the mouse moves, we use a javascript variable to store the click state. In the code snippet below when mouse button is clicked I set clicking variable to true. Once the mouse button is released set clicking to false. And while mouse moves check if clicking is still true (hasn’t been released).

// coordinates of the last drag position
var lastX = 0, lastY = 0;

// record the click event on the image for drag and drop
var clicking = false;

// set clicking to true to record start of drag and drop
// and update the last coordinates
$('#mapHolder').mousedown(function(e){
    	clicking = true;
    	lastX = e.pageX;
    	lastY = e.pageY;
    	$('#mapHolder').css('cursor', 'move');
    	e.stopPropagation();
    	e.preventDefault();
});

// when the mouse button is released set clicking to false
// to stop drag and drop		
$(document).mouseup(function(e){
    	clicking = false;
    	$('#mapHolder').css('cursor', 'default');
    	e.stopPropagation();
    	e.preventDefault();
});

$('#mapHolder').mousemove(function(e){
    	// when mouse if moved check if user is also holding a mouse button
    	if (clicking == false) return;
    		
    	var currentMapPosX = 0, currentMapPosY = 0;
    	
    	// get difference between current and previous mouse position	
    	if ((mapImage.attr('x') <= 0) && (mapImage.attr('x') >= (mapWidth - mapImage.attr('width'))))
    	{
    		currentMapPosX = e.pageX - lastX;
    	}
    		
    	if ((mapImage.attr('y') <= 0) && (mapImage.attr('y') >= (mapHeight - mapImage.attr('height'))))
    	{
    		currentMapPosY = e.pageY - lastY;
    	}
    	
    	// move the image	
    	mapImage.translate(currentMapPosX, currentMapPosY);
    	
    	// record previous position
    	lastX = e.pageX;
    	lastY = e.pageY;
    	
    	// check for image edge	
    	adjustMapEdge();
});		
 
39 Comments

Posted by on April 13, 2011 in JavaScript, RaphaelJS

 

Tags: , , , ,

Hide page content accessibly

Method offered by Jeff Burnz in his AdaptiveThemes article and then improved by community now evolved into a reliable cross-browser technique.

.hidden 
{
    position: absolute !important;
    clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
    clip: rect(1px, 1px, 1px, 1px);
    padding: 0 !important;
    border: 0 !important;
    height: 1px !important;
    width: 1px !important;
    overflow: hidden;
}

This method takes the element out of flow so it doesn’t affect the layout around it. By using height of 1px it fixes the issue with VoiceOver screen reader (Apple) that will not read content of an element that has 0 height.

Though if your gole is to hide content from all users you should use visibility and display of none instead.

.hidden 
{
  visibility: hidden;
  display: none;
}
 
Leave a comment

Posted by on April 11, 2011 in CSS

 

HTML5 Canvas paths mouseover


html5 canvas world map

Another map experiment in HTML5 Canvas this time. The main idea is to draw the world map with paths and fill each path with random colour on mouse hover.

You can see an example of this achieved with RaphaelJS and SVG in my previous post.

With HTML5 Canvas the task is not as straight forward as with SVG and it requires some work to be done to determine that mouse pointer is inside a given path. In theory context.isPointInPath(x, y) function should do just that according to the specification. But if your project has to work in earlier versions of IE and you are using ExCanvas for that then bad news – sPointInPath is not implemented yet.

Big thanks to Sam Hasler from stackoverflow.com for his idea to create second hidden canvas with the same paths, give each path a unique colour and fill the path with this colour. When user moves the mouse over the first visible canvas, get the coordinates of the mouse pointer and the colour of this pixel on the second hidden canvas. If the colour belongs to any of the paths then redraw the canvas and fill the hovered path with it’s colour.

For that we need the following HTML structure:

<!-- Second hidden canvas -->
<div id="shim" style="position:absolute; top: 0; left:0;"> 
    <canvas id="canvas2" width="800" height="500" style="background-color: #000"></canvas> 
</div> 

<!-- First visible canvas -->
<canvas id="canvas" width="800" height="500" style="background-color: #000" onmousemove="mouseMoved(event)"> 
    <p>Your browser doesn't support canvas</p> 
</canvas> 

Note that second canvas has to be located right below the first canvas. So if you position first canvas in the middle of the page, make sure second canvas is also centered.

Then on window load draw the paths on both canvases. But paths on the second canvas have to be filled in with unique colours.

When you user moves the mouse over the canvas, “mouseMoved(event)” function is executed. Here we get the colour of the current pixel, check if colour belongs to existing path and redraw the updated canvas.

function mouseMoved(event)
{
    // get mouse position x,y with a function of your choice
    var mousePos = getMousePosition(event);

    // get the data about pixel on the second canvas		
    var data = context2.getImageData(mousePos[0], mousePos[1], 1, 1).data;
    // get pixel colour
    var color = '#';
    for (var i=0, hex = ''; i<3; i++)
    {
         hex = data[i].toString(16);
         hex = (hex.length==2) ? hex : '0'+hex;
         color+=hex;
    }
    
    // get the path that colour belongs to
    var hoveredCountry = countryColours[color];
    // check that colour is different from colour at previous position e.g. mouse is not over the same country
    if ((currentColour != color) && (visitedCountries[hoveredCountry] === undefined) )
    {
         // blank the canvas
         canvasEl.width = canvasEl.width;
			
         for (var sCountry in oWorldMap) 
         {
             // draw the path
         }
         // update the current colour
         currentColour=color;		
    }
}

The drawback of course is that path colours have to be unique. If background colour is not solid, for example you use gradients, you might want to think of a different method.

View the demo here.

 
Leave a comment

Posted by on April 5, 2011 in HTML

 

Tags: , , , , ,

Creating a world map with RaphaelJS (svg)

SVG and RaphaelJs allow to create really nice experience. Some experimentation on the topic – world map.


svg world map

It’s really incredibly quick and easy to create. All you need is SVG paths to draw the countries. These can easily be found on Wikipedia (look for world map svg files) or paths and selections can be converted to SVG paths with various image editors (like Gimp). Also you can download the paths I used from jsfiddle.

Once you got the paths create a canvas to draw on with RaphaelJS:

var paper = Raphael('mapHolder'); // mapHolder is an ID of div element to be used as canvas

If you use paths from my example you need to include worldpaths.js file to your page and call getPaths function passing default settings for the paths:

var map = getPaths(paper, { fill: "#333", stroke: "#000", "stroke-width": .5, "stroke-linejoin": "round" });

Then loop through the paths array and “draw” SVG paths on canvas:

for (var countryCode in map) {							        
    (function (countryPath) {
        // give paths some opacity look nice
        countryPath.attr({opacity: 0.6});
        
        // get random colour
        colour = Raphael.getColor();
	
        // fill country path with colour when mouse goes over the country path					
        countryPath[0].onmouseover = function() 
        {
             countryPath.animate({fill: colour, stroke: colour }, 300);
             paper.safari();
        };
       
        // return to default grey colour
        countryPath[0].onmouseout = function() 
        {
             countryPath.animate({fill: "#333", stroke: "#000"}, 300);
             paper.safari();
        };
    })(map[countryCode]);
} 

And vuala!

View live demo here.

 
6 Comments

Posted by on April 4, 2011 in RaphaelJS

 

Tags: , ,

 
%d bloggers like this: