Subtitel

A blog by Juri Urbainczyk | Juri on Google+ | Juri on Twitter | Juri on Xing

Showing posts with label Sencha Touch. Show all posts
Showing posts with label Sencha Touch. Show all posts

Tuesday, June 25, 2013

An HTML5 canvas component for ExtJS 4

As I described in another post, ExtJS 4 features components for graphics ("Ext.draw.Component") which are based on the standard HTML SVG tag. But what if we liked to use a <canvas> tag? ExtJS does not bring one of those - thus we have to create one.

We also need a nice little showcase. For that, I implemented a small application, which simulates a solar system, with a configurable number of planets, moons and stars. The application calculates the gravity which affects the objects and then determines their new positions. This is done repeatedly, leading to a simulated continuous motion. The positions (and trajectories) of all objects are displayed in the canvas element. Picture 1 shows the solar system simulator in action.
Picture 1: Solar system simulator written with the canvas component
I put the canvas component in a separate file (canvasPanelClass.js), thus it should be easily reusable. The component utilizes the powerful object-oriented features of ExtJS. It extends Ext.Panel and creates the canvas element in the constructor method. In the afterrender listener (when the component already is complete) it stores a reference to the canvas in a private variable (this.canvas = this.items.items[0].el.dom;).

After that, the canvas is ready to be used. The component already brings some important methods, e.g. drawCircle( 50, 50, 10, "red" ) which draws a red circle with a radius of 10 pixels around the point 50, 50.

And now, the most interesting part of the source code:

        Ext.define('CanvasPanelClass', {
            extend: 'Ext.Panel',
           
            gridColor: '',
            ctx: null,        // is set when rendered
            canvas: null,    // is set when rendered                       
            bodyStyle: { background: '#000' },
            frame: false,
            margin: '2 2 2 2',
           
            listeners: {
                afterrender: {
                    fn: function(){                       
                        this.canvas = this.items.items[0].el.dom;           
                        this.ctx = this.canvas.getContext("2d");                                       this.clear();                       
                    }
                }       
            },           
           
            constructor: function(config) {
               
                //define this here because we can set width and height
                this.items = {
                    xtype: 'box',
                    autoEl:{
                        tag: 'canvas',
                        height: config.height,
                        width: config.width
                    }
                };
               
                this.tempCanvas = document.createElement("canvas");
               
                CanvasPanelClass.superclass.constructor.call(this, config);
            },
                    
            drawRect: function( x, y, width, height, lineWidth, color ) {
                this.ctx.strokeStyle = color;
                this.ctx.lineWidth = lineWidth;
                this.ctx.strokeRect(x,y,width,height);               
            },
           
            fillRect: function( x, y, width, height, color ) {
                this.ctx.fillStyle = color;
                this.ctx.fillRect(x,y,width,height);               
            },       
           
            drawCircle : function( x, y, radius, color ) {
                this.ctx.beginPath();
                this.ctx.strokeStyle = color;
                this.ctx.fillStyle = color;
                this.ctx.arc( x, y, radius, 0, Math.PI*2, true );
                this.ctx.closePath();
                this.ctx.fill();       
            },           
           
            putPixel: function( x, y, size, color ) {
                this.ctx.fillStyle = color;
                this.ctx.fillRect(x,y,size,size);           
            },

                  
            clear: function() {
           
                // Store the current transformation matrix
                this.ctx.save();

                // Use the identity matrix while clearing the canvas
                this.ctx.setTransform(1, 0, 0, 1, 0, 0);
                this.ctx.clearRect ( 0, 0, this.getWidth(), this.getHeight() );

                // Restore the transform
                this.ctx.restore();                                      
            }


        }); // define CanvasPanelClass

Then, the canvas component can be used like in the following code snippet. (There is an array of canvasPanels because you might want to create more than one canvas.) The mousedown handler shows how an application can react on mouse clicks into the canvas.

      canvasPanel[0] = new CanvasPanelClass({
            height: 500,
            width: 500
      });              

      canvasPanel[0].on({
            mousedown: function(DOMevent) {

                             var canvasPanelX = this.getPosition()[0];
                             var canvasPanelY = this.getPosition()[1]
                             var point = DOMevent.getPoint();

                             var eventX = point.x-canvasPanelX;
                             var eventY = point.y-canvasPanelY;

                              // do something …

                             canvasPanel[0].clear();
                             canvasPanel[0].renderGrid(25*50/ZOOM,GRIDCOLOR);
            },
            element: 'body',
            scope: canvasPanel[i] //Ensure "this" is correct during handler execution
      });

      tablePanel.add(canvasPanel[0]); 
  
      canvasPanel[0].clear();    

      canvasPanel[0].putPixel( 50, 50, 1, "green" );

Monday, August 27, 2012

Textfield with an image: how to write a Sencha Touch 2 component


Let's assume we'd want to write a component, which displays a string together with an image. This would come in handy, if we need to display the name of a customer (e.g. 'Pfefferminzia') and it's logo. The following picture shows the component in action - in the second row of the dialog.


We can achieve that by combining an normal textfield ('Ext.field.Text') with a label ('Ext.Label') which contains the corresponding image. Thus, we need a conatiner, which surrounds the textfield and the image and keeps them together. Lucky as we are, Sencha Touch already offers such a component, called 'Ext.Container'. Since we want name and image to appear on the same line one after the other, we choose a 'hbox' layout for our container. At this point, out new component looks like this:
Ext.define("myapp.view.CustomerDisplayField", {
    extend: 'Ext.Container',

    xtype: 'customerdisplayfield',
    
    config: {   
        layout: 'hbox',
        items: [
            {
                xtype: 'textfield',
                label: 'Customer',
                labelWidth: '37.4%',
                readOnly: true,                               
                flex: 4       
            },
            {
                xtype: 'label',
                html: '<span style="float:right; margin-right:10px;"><img height="32px"  src="resources/images/customer.png"/></span>',
                padding: '6 0 0 0',
                flex: 1       
            }       
        ]       
    }
});

This is all very well, but something is missing: we need the image to change accordingly, whenever the customer changes. In order to achieve this, we add a 'change' listener to the text field. Each time the name of the customer is change (e.g. via a call to 'setvalue()') the image should change as well. But, in the 'change' handler we need to call a function on the label sub-component. How do we get a hold on that? We could, for instance, do something like 'this.up().getComponent(1)' but that would make our component dependent an the structure and on the *order* of the sub-compoenents - which is not good.

Therefore, we add an 'initialize' handler to our component and create all the sub-components in this handler.
Thus, we always have a variable which points directly to our 'image label', which make our life quite easy.
We also add a 'setUrl()' method to our image-label and a 'setValue()' method to our component as a whole. Then we are ready and our source code looks like this:
Ext.define("myapp.view.CustomerDisplayField", {
    extend: 'Ext.Container',

    xtype: 'customerdisplayfield',
   
    textfield : null,
    
    config: {   
        layout: 'hbox',
       
        listeners: {
            initialize : function() {
               
                //this label displays the icon
                var mylabel = Ext.create( 'Ext.Label', {
                    padding: '6 0 0 0',
                    flex: 1,
                   
                    setUrl : function(url) {
                      this.setHtml('<span style="float:right; margin-right:10px;"><img height="32px"  src="'+url+'"/></span>');
                    }
                });
               
                //set a default image
                mylabel.setUrl("resources/images/warning.png");
               
                //this textfield displays the customer's name
                var mytext = Ext.create('Ext.field.Text',{
                    label: 'Customer',
                    labelWidth: '37.4%',
                    readOnly: true,                               
                    flex: 4,
                    listeners: {   

                        change: function(field,newvalue,oldvalue) {
                            //get the correct url for my new customer
                            var url = customernameToUrl(newvalue);                       
                            mylabel.setUrl(url);                       
                        }
                    }
                });                       
               
                this.add(mytext);
                this.add(mylabel);
               
                this.textfield = mytext;
            }
        }       
    },
   
    setValue : function(newvalue) {
        this.textfield.setValue(newvalue);
    }
});

Monday, August 20, 2012

10 Golden Rules when working with Sencha Touch and JavaScript

Lately I had the chance to work extensively with Sencha Touch again, which is really a great framework. Here is some of the advice I have to offer:

1. Be mindful of brackets
If you open a bracket always close it immediately. And only then fill in the respective code between the brackets. The same applies for quotation marks. Even Better: Use an editor which comes with a sound support for JavaScript syntax highlighting and which can automatically close opened brackets. This will spare you a lot of trouble and save a lot of time which you would spend otherwise looking for the missing comma.

2. Don't forget: your developing for smartphones!
Dont't put to much widgets on you screens. Mobile devices are smaller than you think.

3. Use classes
 Use the class system of Sencha touch and extend the existing components. Extract you code into these new widgets. This will help to keep your code small and beautiful. The structure of you views should always be visible right out of the source code.

4. Run your app and play with it
Develop using Chrome and Safari. Test on native devices repeatedly. You will always be in for suprises.

5. Stay flexible
Dont't use fixed sizes for your components, employ relative sizes instead. E.g. never use "width:500px" but rather "width:20%". Fixed sizes will get you into trouble very swiftly.

6. Structure your app
Structure your application with the existing system of views, models and stores. Alayws start with the models. Use the convert option if you need special data for visualization which you did not get from the server.

7. Keep classes small.
Classes should never be longer than 2-3 pages in your editor of choice.

8. Use dynamic loading
Always fill in the "extends" attributes in order to enable Sencha Touch dynamic loading.

9. Use logging
Use comsole.log for early feedback during development. This is especially important for keeping track of store loading and initializing.

10. Don't reinvent the wheel
Use setRecord() and getRecord() to transfer data between stores and panels.

Saturday, January 28, 2012

„RIA goes mobile“ : My article on Sencha Touch published in Mobile Technology


In the last years I had the chance to develop some applications with the ExtJS RIA framework and I was deeply impressed by the flexibility and power of the framework. I think, that the programming model, meaning the way how a programmer can use the framework to implement logic and presentation, is largely the reason for this. Therefore, it was quite clear to me, that I had to try out Sencha Touch as well, since it is simply put ExtJS for mobile devices.

Sencha Touch is a framework for developing mobile web applications on the basis of HTML5, CSS3 and JavaScript. The resulting web applications can be run on nearly any mobile browser, eliminating the differences of the platforms for the developer. Thus, you only need one code base and don’t have to develop a separate application for every platform.

My article can be found in full length in the January issue of “Mobile Technology” magazine (http://mobile360.de/mobiletechnology). The article contains an in depth description of the framework, using a web app I implemented for A:gon Solutions (http://www.agon-solutions.de) as an example. The idea behind the app ist hat the company needed a possibility to gain feedback on the many customer events. In the app the customer can score the presentations and talks of the event and give recommendations on what he expects from the next workshops. Actually, the web app is deployed on Google App Engine which was a first timer for me (http://agonfeedback.appspot.com/).
Considering the special situation, a mobile user finds him in, the design of the web app focuses on ease of use and simplification. Therefore, I abandoned little checkboxes and complex selection boxes in favor of big buttons and clear symbols. Also, swipe gestures are used to switch from one page to another or back, if needed. Sencha Touch really makes this easy, by carefully integrating these “swipe” events into the standard programming model of the framework.

Working with the Sencha Touch really is fun, especially if you are already acquainted with ExtJS or another similar JavaScript framework. With this kind of experience getting into the framework and coming up with first results is a matter of hours at most. Only some lines of code are needed in order to build complete application Frontends. The following code snippet shows a class, which defines the basic properties of the app:

var App = new Ext.Application({     
    name               : 'FeedbackApp',
    defaultUrl         : 'index.html',     
    launch             : function () {                               
          FeedbackApp.ViewportClass = Ext.extend(Ext.Panel, {
                fullscreen: true,
                items: [ carousel ] // carousel defined elsewhere         
          });                
          this.viewport = new FeedbackApp.ViewportClass();     
   }
});      

As a mobile framework, Sencha Touch also contains elements to cope with device orientation (landscape vs portrait). Although this is possible it proved to be not so easy as the rest and maybe needs some more thinking on the side of the framework developers. One more lesson I learned while doing the app: don’t use absolute sizes for your GUI elements since you will run into trouble on the very first phone you did not test it with.