<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Bryntum</title>
	<atom:link href="http://www.bryntum.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.bryntum.com</link>
	<description>Tools for Enterprise JavaScript Development</description>
	<lastBuildDate>Tue, 07 May 2013 13:25:14 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5</generator>
		<item>
		<title>Mocking Ajax calls with Siesta</title>
		<link>http://www.bryntum.com/blog/mocking-ajax-calls-with-siesta/</link>
		<comments>http://www.bryntum.com/blog/mocking-ajax-calls-with-siesta/#comments</comments>
		<pubDate>Tue, 07 May 2013 13:16:32 +0000</pubDate>
		<dc:creator>Mats Bryntse</dc:creator>
				<category><![CDATA[Siesta]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[Ext JS]]></category>
		<category><![CDATA[mock]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2907</guid>
		<description><![CDATA[<p>In the past couple of months we&#8217;ve received a few requests for mocking capabilities when it comes to Ajax requests with Siesta. We are actually quite interested in this feature ourself so we we&#8217;re very happy when we realized how easy it was to accomplish. The solution: Ext.ux.ajax.SimManager, is found in the Ext JS examples/ux folder.</p> <p></p> Introducing the Ext.ux.ajax.SimManager <p>This class (<a href="http://docs.sencha.com/extjs/4.2.0/#!/api/Ext.ux.ajax.SimManager">docs here</a>) has actually been around for ...]]></description>
				<content:encoded><![CDATA[<p>In the past couple of months we&#8217;ve received a few requests for mocking capabilities when it comes to Ajax requests with Siesta. We are actually quite interested in this feature ourself so we we&#8217;re very happy when we realized how easy it was to accomplish. The solution: <code>Ext.ux.ajax.SimManager</code>, is found in the Ext JS examples/ux folder.</p>
<p><img src="http://www.bryntum.com/wp-content/uploads/2013/05/Screen-Shot-2013-05-07-at-2.31.35-PM-e1367933015612.png" alt="Screen Shot 2013-05-07 at 2.31.35 PM" width="150" class="alignnone size-full wp-image-2908" /></p>
<h4>Introducing the Ext.ux.ajax.SimManager</h4>
<p>This class (<a href="http://docs.sencha.com/extjs/4.2.0/#!/api/Ext.ux.ajax.SimManager">docs here</a>) has actually been around for quite a while, and it is even well documented in the public Sencha docs though we didn&#8217;t really notice it until recently. Originally written by the Ext JS mastermind <a href="https://twitter.com/dongryphon">Don Griffin</a>, this class provides very easy mocking support. The static SimManager class allows you to easily mount a simulated Ajax response referred to as a <em>simlet</em>, mapped to a given URL. To mount a dataset for a specific URL returning a basic JSON object, you call the init method as seen below:</p>
<p></p><pre class="crayon-plain-tag">Ext.ux.ajax.SimManager.init({
        delay : 300
    }).register(
        {
            '/app/data/url' : {
                stype : 'json',  // use JsonSimlet (stype is like xtype for components)
                data  : [
                    { id : 1, name : 'user1', age : 25 },
                    { id : 2, name : 'user2', age : 35 },
                    { id : 3, name : 'user3', age : 45 }
                ]
            }
        }
    );</pre><p></p>
<p>We can specify the delay to use, and just pass in the data we&#8217;d like the mocked response to contain. Later in your test when your store (or plain Ajax call) requests the &#8216;/app/data/url&#8217; URL, this will be handled automatically by the SimManager. To make use of the SimManager class in a Siesta test, simply configure the &#8216;loaderPath&#8217; to be able to <em>require</em> it inside the test.</p>
<p></p><pre class="crayon-plain-tag">{
        url : '051_ext-ajax-mock.t.js',

        // Setup the path to the 'ux' folder on the Sencha CDN
        loaderPath  : { 'Ext.ux' : 'http://cdn.sencha.io/ext-4.2.0-gpl/examples/ux' }
    }</pre><p></p>
<p>I just created a basic Siesta test (included in the next release) to the Ext JS examples folder of the Siesta SDK to prove how easy it is to get started. All we have to do is to &#8216;require&#8217; the SimManager class first.</p>
<p></p><pre class="crayon-plain-tag">StartTest(function (t) {

    t.requireOk('Ext.ux.ajax.SimManager', function () {
        Ext.define('MyModel', {
            extend : 'Ext.data.Model',
            fields : [
                'id',
                'name',
                'age'
            ]
        });

        Ext.ux.ajax.SimManager.init({
            delay : 300
        }).register(
            {
                '/app/data/url' : {
                    stype : 'json',  // use JsonSimlet (stype is like xtype for components)
                    data  : [
                        { id : 1, name : 'user1', age : 25 },
                        { id : 2, name : 'user2', age : 35 },
                        { id : 3, name : 'user3', age : 45 }
                    ]
                }
            }
        );

        var store = new Ext.data.Store({
            model : 'MyModel',

            proxy : {
                type : 'ajax',
                url  : '/app/data/url' // doesn't exist
            }
        });

        t.willFireNTimes(store, 'load', 1);

        t.it('should be possible to load mock data', function (t) {
            t.loadStoresAndThen(store, function () {
                t.expect(store.first().get('id')).toBe(1);
                t.expect(store.first().get('name')).toBe('user1');
                t.expect(store.getAt(1).get('id')).toBe(2);
                t.expect(store.getAt(1).get('name')).toBe('user2');
                t.expect(store.getAt(2).get('id')).toBe(3);
                t.expect(store.getAt(2).get('name')).toBe('user3');
            });
        });
    });
});</pre><p></p>
<p>You can simulate any number of URL&#8217;s and associate them with an Ext.ux.ajax.Simlet. To make non-simulated Ajax requests after the SimManager class is initialized, add a <code>nosim : true</code> option to your Ajax call config.</p>
<h4>Summing up</h4>
<p>Using mocked Ajax calls are a great way to simplify your testing and speed it up at the same time. We hope you will find this Ext JS feature useful in your test suite. Do you have any other mocking tips &#038; tricks? Please let us know in the comments or our support forums.</p>
<h4>Related links:</h4>
<p><a href="http://docs.sencha.com/extjs/4.2.0/#!/api/Ext.ux.ajax.SimManager">Ext.ux.ajax.SimManager docs</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/mocking-ajax-calls-with-siesta/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>describe(&#8220;Siesta 1.2.0 with BDD support&#8221;)</title>
		<link>http://www.bryntum.com/blog/describesiesta-1-2-0-with-bdd-support/</link>
		<comments>http://www.bryntum.com/blog/describesiesta-1-2-0-with-bdd-support/#comments</comments>
		<pubDate>Tue, 23 Apr 2013 13:42:38 +0000</pubDate>
		<dc:creator>Nickolay</dc:creator>
				<category><![CDATA[Siesta]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[BDD]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2743</guid>
		<description><![CDATA[<p>BDD, or <em><a href="http://en.wikipedia.org/wiki/Behavior-driven_development">Behavior Driven Development</a></em> has been all the hype for a while now when it comes to JavaScript development. One of the most known JavaScript BDD test tools is Jasmine, and it does a great job of producing readable tests. We&#8217;ve received lots of feedback from the Siesta community to add support for writing BDD tests in Siesta. Since we&#8217;re getting close to releasing the 1.2 version of ...]]></description>
				<content:encoded><![CDATA[<p>BDD, or <em><a href="http://en.wikipedia.org/wiki/Behavior-driven_development">Behavior Driven Development</a></em> has been all the hype for a while now when it comes to JavaScript development. One of the most known JavaScript BDD test tools is Jasmine, and it does a great job of producing readable tests. We&#8217;ve received lots of feedback from the Siesta community to add support for writing BDD tests in Siesta. Since we&#8217;re getting close to releasing the 1.2 version of Siesta (which is a pretty big release) &#8211; let&#8217;s take a look at the new BDD support in this version. </p>
<h5>Hello BDD world </h5>
<p>To make tests better describe what is being tested we use 3 new methods to arrange our test code. At the top level, to describe a &#8220;suite&#8221; of tests we can call <code>t.describe</code>, and pass it a string describing the suite and as the second argument we&#8217;ll pass a function having the test instance as its only argument.</p>
<p></p><pre class="crayon-plain-tag">describe("My test suite", function (t) {
    t.pass('BDD FTW')
});</pre><p></p>
<p>Inside the suite, we can have other sub-suites or any valid Siesta test assertion statements. To divide a suite into logical blocks, we use what&#8217;s called a <em>spec</em>. As in Jasmine, inside a spec you typically have sub-specs or one or more <code>expect</code> statements to verify your assumptions. An expect statement begins with the value to assert, followed by the matcher. See the code below for a simple demonstration.</p>
<p></p><pre class="crayon-plain-tag">describe("My test suite", function (t) {
    var doc = document,
        body = doc.body;

    t.it('Should be possible to set a CSS class on body', function(t) {
        body.className = 'foo';

        t.expect(doc.getElementsByClassName('foo')).toContain(body);
    });
});</pre><p></p>
<p>The full list of matchers can be seen below (and they will also be described in the docs):</p>
<ul class="list_arrow">
<li>toBe</li>
<li>toEqual</li>
<li>toBeNull</li>
<li>toBeNan</li>
<li>toBeDefined</li>
<li>toBeUndefined</li>
<li>toBeTruthy</li>
<li>toBeFalsy</li>
<li>toMatch</li>
<li>toContain</li>
<li>toBeLessThan</li>
<li>toBeMoreThan</li>
<li>toBeCloseTo</li>
<li>toThrow</li>
</ul>
<h5 style="margin-top:20px">BDD UI tests</h5>
<p>If you want to write more complex tests using chains of UI actions, those commands will be queued and executed in sequence. Let&#8217;s look at how this could be done. In the test case below, a simple input field is rendered together with a button. If the text is not a number, the field should receive an &#8216;invalid&#8217; CSS class. The code below verifies the two cases, of valid vs invalid input. Note that we are using the <code>not</code> property to alter the meaning of the <code>toContain</code> expectation in the second spec.</p>
<p></p><pre class="crayon-plain-tag">describe("My test suite", function (t) {

    function prepareBody(next) {
        document.body.innerHTML = '<h4>Input your age</h4>' +
                                  '<input class="input-age" type="text" style="width:30px" />' +
                                  '<input class="submit-button" value="Submit" type="button" />';

        // Simple click handler
        t.$('.submit-button').click(function(){
            if (isNaN(parseInt(t.$(".input-age").val(), 10))){
                t.$(".input-age").addClass('invalid');
            } else {
                t.$(".input-age").removeClass('invalid');
            }
        });

        next();
    }

    t.it('Should find an "invalid" CSS class on the age input for invalid input', function(t) {

        t.chain(
            prepareBody,

            { action : 'type', text : 'a', target : '.input-age' },
            { action : 'click', target : '.submit-button' },

            function() {
                t.expect(document.getElementsByClassName('input-age')[0].className).toContain('invalid');
            }
        )
    });

    t.it('Should not see the "invalid" CSS class on the age input for proper input', function(t) {

        t.chain(
            prepareBody,

            { action : 'type', text : '25', target : '.input-age' },
            { action : 'click',  target : '.submit-button' },

            function() {
                t.expect(document.getElementsByClassName('input-age')[0].className).not.toContain('invalid');
            }
        )
    });
});</pre><p></p>
<p>In the Siesta UI, this is what we see:</p>
<p><a href="http://www.bryntum.com/blog/describesiesta-1-2-0-with-bdd-support/screen-shot-2013-04-22-at-4-14-33-pm/" rel="attachment wp-att-2884"><img src="http://www.bryntum.com/wp-content/uploads/2013/04/Screen-Shot-2013-04-22-at-4.14.33-PM.png" alt="Screen Shot 2013-04-22 at 4.14.33 PM" width="100%" class="alignnone size-full wp-image-2884" /></a></p>
<p>Passed specs are collapsed by default, and if a spec fails it&#8217;s automatically rendered in expanded state so you can see the failed expectation easily.</p>
<p>By using the BDD syntax above, the test becomes very readable as each sub-section of the test is decorated with its purpose up front. But, since code readability is quite subjective by nature, you may or not not prefer this syntax over plain simple JS code in your tests. </p>
<p>What you do you think, will you be using this BDD syntax in your test suite after v1.2 is released? Let us know what&#8217;s on your mind!</p>
<p>Download the 1.2 beta-2 release here:</p>
<p><a href="http://bryntum.com/siesta-1.2.0-beta-2-lite.zip">Siesta Lite</a><br />
<a href="http://bryntum.com/siesta-1.2.0-beta-2-trial.zip">Standard trial</a> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/describesiesta-1-2-0-with-bdd-support/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Using Ext Scheduler in Sencha Architect</title>
		<link>http://www.bryntum.com/blog/using-ext-scheduler-in-sencha-architect/</link>
		<comments>http://www.bryntum.com/blog/using-ext-scheduler-in-sencha-architect/#comments</comments>
		<pubDate>Wed, 03 Apr 2013 21:50:50 +0000</pubDate>
		<dc:creator>Mats Bryntse</dc:creator>
				<category><![CDATA[Tips 'n tricks]]></category>
		<category><![CDATA[Ext Scheduler]]></category>
		<category><![CDATA[Sencha Architect]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2789</guid>
		<description><![CDATA[<p>We&#8217;ve been asked quite a lot how to use our components inside Sencha Architect. With the recent release of Sencha Architect 2.2, this is now very easy to solve. Please note that third party components still aren&#8217;t fully supported by Architect and they won&#8217;t render in the canvas (but it&#8217;s still very useful). If you&#8217;re in a rush and just want to see how this is done, you can see ...]]></description>
				<content:encoded><![CDATA[<p>We&#8217;ve been asked quite a lot how to use our components inside Sencha Architect. With the recent release of Sencha Architect 2.2, this is now very easy to solve. Please note that third party components still aren&#8217;t fully supported by Architect and they won&#8217;t render in the canvas (but it&#8217;s still very useful). If you&#8217;re in a rush and just want to see how this is done, you can see me do it in this simple YouTube video.</p>
<div class="video1" style="width: 550px; height: 300px; padding: 6px 0 0 6px; border: 1px solid #dbdbdb; background: #fff; box-shadow: 1px 1px 1px #dbdbdb;" ><iframe title="YouTube video player" width="545" height="295" src="http://www.youtube.com/embed/TTMLHhISGrQ?wmode=transparent&amp;modestbranding=1&amp;autohide=1&amp;rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe></div>
<p>&nbsp;</p>
<p>If you prefer reading, let&#8217;s get started.</p>
<h5 style="margin: 15px 0;">Launching Architect 2.2</h5>
<p>First of all, we create a new Architect project using Ext JS 4.2. We then drag a simple Ext.Panel onto the canvas to serve as our example container for this demo. Remember to set the &#8216;layout&#8217; property of this panel to &#8216;fit&#8217;. The Architect UI should look something like this:</p>
<p><a href="http://www.bryntum.com/blog/using-ext-scheduler-in-sencha-architect/screen-shot-2013-04-03-at-8-53-12-pm/" rel="attachment wp-att-2792"><img class="alignnone size-medium wp-image-2792" alt="Screen Shot 2013-04-03 at 8.53.12 PM" src="http://www.bryntum.com/wp-content/uploads/2013/04/Screen-Shot-2013-04-03-at-8.53.12-PM.png" width="295" height="300" /></a></p>
<p>We then continue to add another Ext.Panel to the top panel. Then we select the child panel in the canvas so we can add some config properties to it, like setting its title to My Scheduler. Now it&#8217;s time for the first trick to get this working: <strong>createAlias</strong>.</p>
<p><a href="http://www.bryntum.com/blog/using-ext-scheduler-in-sencha-architect/screen-shot-2013-04-03-at-9-01-24-pm/" rel="attachment wp-att-2797"><img class="alignnone size-full wp-image-2797" alt="Screen Shot 2013-04-03 at 9.01.24 PM" src="http://www.bryntum.com/wp-content/uploads/2013/04/Screen-Shot-2013-04-03-at-9.01.24-PM.png" width="300" /></a></p>
<p>The &#8216;createAlias&#8217; option allows us to set a custom xtype for a component which is actually just what we need. We can now set this value to &#8216;<strong>schedulergrid</strong>&#8216; which maps to our Sch.panel.SchedulerGrid constructor. If we were to save this, build and run it now, we&#8217;d get a cryptic error saying</p>
<blockquote><p>&#8220;Uncaught TypeError: Cannot call method &#8216;substring&#8217; of undefined&#8221;.</p></blockquote>
<p>This is the Ext JS way of saying &#8220;I don&#8217;t understand&#8221;. And while the error message is cryptic, once we know what Ext JS is trying to tell us &#8211; it makes sense since we haven&#8217;t added any references to the Ext Scheduler resources.</p>
<h5 style="margin: 15px 0;">Importing the Ext Scheduler resources</h5>
<p>In the Project Inspector panel, locate and select the Resources node and then hit the + symbol in the toolbar above. Add one JS resource and one CSS resource. Point the JS resource to your local copy of sch-all-debug.js and do the same for the CSS resource (resources/css/sch-all.css). The Project Inspector panel should now look like this:</p>
<p><a href="http://www.bryntum.com/blog/using-ext-scheduler-in-sencha-architect/screen-shot-2013-04-03-at-9-11-16-pm/" rel="attachment wp-att-2800"><img class="alignnone size-full wp-image-2800" alt="Screen Shot 2013-04-03 at 9.11.16 PM" src="http://www.bryntum.com/wp-content/uploads/2013/04/Screen-Shot-2013-04-03-at-9.11.16-PM.png" width="300" /></a></p>
<h5 style="margin: 15px 0;">Adding Ext Scheduler config properties</h5>
<p>If you build and publish the project and try to run it at this point, you&#8217;ll see this error in the console.</p>
<blockquote><p>Uncaught Sch.mixin.AbstractSchedulerPanel.initStores(): You must specify a resourceStore config</p></blockquote>
<p>Don&#8217;t worry &#8211; this is also expected since we haven&#8217;t yet configured the Scheduler properly (it requires a resource store and an event store). To add simple custom config properties, we can just use the textbox seen at the top of the Config panel (try adding &#8216;startDate&#8217;, &#8216;endDate&#8217; and &#8216;viewPreset&#8217;). To provide the Scheduler with complex config properties such as the custom stores it needs, we open the Config panel and locate the &#8216;processMyScheduler&#8217; function hook. Click the <strong>+</strong> symbol next to it to add this hook to the component. You should now see it located in Project Inspector above, go ahead and double click it. This opens the code editor:</p>
<p><a href="http://www.bryntum.com/blog/using-ext-scheduler-in-sencha-architect/screen-shot-2013-04-03-at-9-16-33-pm/" rel="attachment wp-att-2805"><img class="alignnone size-full wp-image-2805" style="max-width: 100%;" alt="Screen Shot 2013-04-03 at 9.16.33 PM" src="http://www.bryntum.com/wp-content/uploads/2013/04/Screen-Shot-2013-04-03-at-9.16.33-PM.png" width="100%" /></a></p>
<p>As you can see, the default implementation receives the scheduler config object and simply returns it as is. We can now apply our own config code easily:</p>
<p></p><pre class="crayon-plain-tag">processMyScheduler: function (config) {
    var startDate = config.startDate || new Date();

    return Ext.applyIf(config, {
        colums: [{
                header: 'Name',
                dataIndex: 'Name'
            }
        ],

        resourceStore: new Sch.data.ResourceStore({
            // Here you'll configure your resource store properties
            // for now we use dummy data
            data: [{
                    Id: 1,
                    Name: 'Mike'
                }, {
                    Id: 2,
                    Name: 'Kate'
                }
            ]
        }),
        eventStore: new Sch.data.EventStore({
            // Here you'll configure your event store properties
            // for now we use dummy data
            data: [{
                    ResourceId: 1,
                    Name: 'Bake the cake',
                    StartDate: startDate,
                    EndDate: Ext.Date.add(startDate, Ext.Date.DAY, 5)
                }, {
                    ResourceId: 2,
                    Name: 'Brew some coffee',
                    StartDate: startDate,
                    EndDate: Ext.Date.add(startDate, Ext.Date.DAY, 5)
                }
            ]
        })
    });
}</pre><p></p>
<p>We simply create a grid column and 2 stores and fill them with some basic dummy data to make sure all is working fine. If you try to run it now, you should see something like this:</p>
<p><a href="http://www.bryntum.com/blog/using-ext-scheduler-in-sencha-architect/screen-shot-2013-04-03-at-9-48-32-pm/" rel="attachment wp-att-2830"><img class="alignnone size-full wp-image-2830" alt="Screen Shot 2013-04-03 at 9.48.32 PM" src="http://www.bryntum.com/wp-content/uploads/2013/04/Screen-Shot-2013-04-03-at-9.48.32-PM.png" width="806" /></a></p>
<p>&#8230;and the mission is accomplished! To understand what was actually generated behind the scenes, let&#8217;s open the generated source file in MyPanel.js.</p>
<p></p><pre class="crayon-plain-tag">/*
 * File: app/view/MyPanel.js
 *
 * This file was generated by Sencha Architect version 2.2.0.
 * http://www.sencha.com/products/architect/
 *
 * This file requires use of the Ext JS 4.2.x library, under independent license.
 * License of Sencha Architect does not include license for Ext JS 4.2.x. For more
 * details see http://www.sencha.com/license or contact license@sencha.com.
 *
 * This file will be auto-generated each and everytime you save your project.
 *
 * Do NOT hand edit this file.
 */
Ext.define('MyApp.view.MyPanel', {
    extend: 'Ext.panel.Panel',

    height: 250,
    width: 400,
    layout: {
        type: 'fit'
    },
    title: 'My Panel',

    initComponent: function () {
        var me = this;

        Ext.applyIf(me, {
            items: [
                    me.processMyScheduler({
                    xtype: 'schedulergrid',
                    title: 'My Scheduler'
                })
            ]
        });

        me.callParent(arguments);
    },

    processMyScheduler: function (config) {
        var startDate = config.startDate || new Date();

        return Ext.applyIf(config, {
            columns: [{
                    header: 'Name',
                    dataIndex: 'Name'
                }
            ],

            resourceStore: new Sch.data.ResourceStore({
                // Here you'll configure your resource store properties
                // for now we use dummy data
                data: [{
                        Id: 1,
                        Name: 'Mike'
                    }, {
                        Id: 2,
                        Name: 'Kate'
                    }
                ]
            }),
            eventStore: new Sch.data.EventStore({
                // Here you'll configure your event store properties
                // for now we use dummy data
                data: [{
                        ResourceId: 1,
                        Name: 'Bake the cake',
                        StartDate: startDate,
                        EndDate: Ext.Date.add(startDate, Ext.Date.DAY, 5)
                    }, {
                        ResourceId: 2,
                        Name: 'Brew some coffee',
                        StartDate: startDate,
                        EndDate: Ext.Date.add(startDate, Ext.Date.DAY, 5)
                    }
                ]
            })
        });
    }
});</pre><p></p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/using-ext-scheduler-in-sencha-architect/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The 2.2 release is here</title>
		<link>http://www.bryntum.com/blog/the-2-2-release-is-here/</link>
		<comments>http://www.bryntum.com/blog/the-2-2-release-is-here/#comments</comments>
		<pubDate>Fri, 29 Mar 2013 10:26:43 +0000</pubDate>
		<dc:creator>Mats Bryntse</dc:creator>
				<category><![CDATA[Ext Scheduler]]></category>
		<category><![CDATA[Product updates]]></category>
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2754</guid>
		<description><![CDATA[<p>Today we&#8217;re happy to announce the new 2.2 release of Ext Scheduler and Ext Gantt. Both products have been upgraded to support Ext JS 4.2 (v4.2 GA is required). A lot of effort went into this release, to clean up and remove old Ext JS overrides, so supporting future versions of Ext JS will require much less work. You can also read about some of the internal changes for 2.2 ...]]></description>
				<content:encoded><![CDATA[<p>Today we&#8217;re happy to announce the new 2.2 release of Ext Scheduler and Ext Gantt. Both products have been upgraded to support Ext JS 4.2 (v4.2 GA is required). A lot of effort went into this release, to clean up and remove old Ext JS overrides, so supporting future versions of Ext JS will require much less work. You can also read about some of the internal changes for 2.2 in a few previous <a href="/blog/road-to-ext-scheduler-2-2-upgrade-guide/">blog posts</a>. </p>
<h4>Main changes for Ext Scheduler</h4>
<p>For the Scheduler, this release includes a number of new goodies:</p>
<h5>RTL support</h5>
<p>Since Ext JS 4.2 supports RTL we also added support for this, and you can try this in the new <a href="/examples/scheduler-latest/examples/rtl/rtl.html">&#8216;RTL&#8217; demo</a>.</p>
<p><a href="/examples/scheduler-latest/examples/rtl/rtl.html"><img src="http://www.bryntum.com/wp-content/uploads/2013/03/Screen-Shot-2013-03-29-at-10.50.49-AM.png" alt="Screen Shot 2013-03-29 at 10.50.49 AM" width="535" class="alignnone size-large wp-image-2756" /><br />
</a></p>
<h5>Neptune demo</h5>
<p>We now support a &#8220;no-theme&#8221; version, where no styling is applied to the rendered event bars. This made it easy to support the new Neptune theme too. Open the new <a href="/examples/scheduler-latest/examples/theming/theming.html">&#8216;theming&#8217; demo</a> to try it out.</p>
<p><a href="/examples/scheduler-latest/examples/theming/theming.html"><img src="http://www.bryntum.com/wp-content/uploads/2013/03/theme.png" alt="theme" width="535" class="alignnone size-full wp-image-2763" /><br />
</a></p>
<h5>IE 10 support</h5>
<p>Ext JS 4.2 adds support for IE 10, and we have updated our components to support it too. We also added a new IE10 VM to our test farm. In Ext JS 4.2, there is no &#8220;.x-ie&#8221; tag added to the body, instead there is a &#8220;.x-ie10&#8243; tag. Make sure you review all your uses of &#8216;Ext.isIE&#8217; too.</p>
<h4>Gantt</h4>
<p>Ext Gantt also received a lot of refactoring love in this release. We removed a lot of the hard coded CSS used for setting dimensions to the rendered elements. As a result, we now support setting a custom row height on the Gantt panel which can be useful to fit more content vertically. In the updated &#8216;gantt-scheduler&#8217; demo, we added a settings panel where you can try this feature out.</p>
<p><img src="http://www.bryntum.com/wp-content/uploads/2013/03/Screen-Shot-2013-03-29-at-11.11.23-AM.png" alt="Screen Shot 2013-03-29 at 11.11.23 AM" width="535" class="alignnone size-full wp-image-2767" /></p>
<h5>Easier styling</h5>
<p>We also removed most of the images used in the Gantt chart so now you can style pretty much every aspect of the chart without using images (using images makes it harder to theme). The milestones are now rendered using pure HTML (the HTML produced varies slightly between legacy IE and good browsers).</p>
<p><img src="http://www.bryntum.com/wp-content/uploads/2013/03/Screen-Shot-2013-03-29-at-11.10.28-AM.png" alt="Screen Shot 2013-03-29 at 11.10.28 AM" width="434" class="alignnone size-full wp-image-2770" /></p>
<h5>Task editor widget</h5>
<p>Similar to the popular &#8216;EventEditor&#8217; widget in Ext Scheduler, we have now added a task editing plugin to Ext Gantt. </p>
<p><img src="http://www.bryntum.com/wp-content/uploads/2013/03/Screen-Shot-2013-03-29-at-11.15.20-AM.png" alt="Screen Shot 2013-03-29 at 11.15.20 AM" width="535" class="alignnone size-full wp-image-2772" /></p>
<p>Using this plugin, you can edit all data related to a task (task data fields, duration, assignments, dependencies etc). You can try double clicking a task in the <a href="/examples/gantt-latest/examples/advanced/advanced.html">&#8216;advanced&#8217;</a> demo to try it out.</p>
<h4>Go give it a try</h4>
<p>The new release is available in the <a href="/customerzone">customer zone</a>, where you can find all our new releases as well as nightly builds. We&#8217;d really like to hear your feedback on this release, and how we can continue to improve our products. Please raise your voice in our <a href="/forum">forums</a> if you have any ideas or feedback.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/the-2-2-release-is-here/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Introducing UberGrid</title>
		<link>http://www.bryntum.com/blog/introducing-ubergrid/</link>
		<comments>http://www.bryntum.com/blog/introducing-ubergrid/#comments</comments>
		<pubDate>Fri, 01 Mar 2013 05:00:40 +0000</pubDate>
		<dc:creator>Nickolay</dc:creator>
				<category><![CDATA[Product updates]]></category>
		<category><![CDATA[UberGrid]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2414</guid>
		<description><![CDATA[<p></p> Introduction <p>We&#8217;re proud to present the new <a href="/products/ubergrid/" target="_blank">UberGrid</a> component &#8211; a grid with super powers for Sencha Touch! UberGrid aims to support as many grid features as possible while also providing excellent performance on a wide range of mobile devices.</p> <p>The UberGrid is the latest member of our product family. It already serves as the base component for another of our products &#8211; the <a href="/blog/touch-scheduler-1-0/" target="_blank">Touch ...]]></description>
				<content:encoded><![CDATA[<p><img src="http://www.bryntum.com/wp-content/uploads/2013/02/ubergrid.png" alt="ubergrid" class="alignnone size-large wp-image-2631" width="535" /></p>
<h4>Introduction</h4>
<p>We&#8217;re proud to present the new <a href="/products/ubergrid/" target="_blank">UberGrid</a> component &#8211; a grid with super powers for Sencha Touch! UberGrid aims to support as many grid features as possible while also providing excellent performance on a wide range of mobile devices.</p>
<p>The UberGrid is the latest member of our product family. It already serves as the base component for another of our products &#8211; the <a href="/blog/touch-scheduler-1-0/" target="_blank">Touch Scheduler</a>. We decided that the two products would benefit from a clean separation of logic &#8211; the scheduler just uses the grid API and doesn&#8217;t need to worry about the underlying grid implementation. The grid on the other hand &#8211; will not &#8220;know&#8221; anything about scheduling, thus remaining an isolated and general-purpose grid.</p>
<p>The UberGrid is mainly targeted at tablet devices and &#8216;phablets&#8217;, since grids normally display a lot of information. There is of course nothing preventing you from using it on a supported smartphone though.</p>
<h4>Features</h4>
<p>For the initial 1.0 release, the UberGrid supports:</p>
<ul>
<li>
     <strong>Grouped column headers</strong><br />
The columns in the UberGrid can have sub-columns and form groups. There is no limit on the number of nesting levels.</p>
<p><img src="http://www.bryntum.com/wp-content/uploads/2013/02/header.png" alt="header" width="535" class="alignnone size-large wp-image-2678" />
        </li>
<li><strong>Buffered rendering</strong>
<p>UberGrid supports a special rendering mode in which only the rows visible in the viewport are included in the DOM. In this mode, the UberGrid offers a flat performance for any number of rows in the data set. For small data sets, you can just use the default non-buffered rendering mode which provides the best possible scrolling experience. See the below DOM footprint for a grid with a viewport fitting 5 rows, while having 100s of records in its store.</p>
<p><img src="http://www.bryntum.com/wp-content/uploads/2013/02/Screen-Shot-2013-02-28-at-11.44.17-AM.png" alt="Screen Shot 2013-02-28 at 11.44.17 AM" width="535" class="alignnone size-full wp-image-2694" /></p>
<li><strong>Unlimited number of locked sections</strong>
<p>The UberGrid can have several &#8220;locked&#8221; sections on the left or the right side of the main content area. In the image below, there is a scrollable section on both sides.</p>
<p><img src="http://www.bryntum.com/wp-content/uploads/2013/02/Screen-Shot-2013-02-28-at-12.02.18-PM.png" alt="Screen Shot 2013-02-28 at 12.02.18 PM" width="535" class="alignnone size-full wp-image-2704" /></p>
<li><strong>Unit tested with <a href="http://bryntum.com/products/siesta/" target="_blank">Siesta</a></strong>
<p>Code stability is a feature. The UberGrid is covered with unit and functional tests and the test suite is included in the package. We run the test suite nightly and <em>before every release</em>. This makes sure we won&#8217;t accidentally break old features in new releases and any bugs found should only be reported once &#8211; as each bug introduces a new test case. You can count on the public (documented) API to remain stable in future releases.</p>
<p><a href="http://www.bryntum.com/blog/introducing-ubergrid-2/ug-unit-tested-2/" rel="attachment wp-att-2650"><img class="size-full wp-image-2650 alignnone" alt="ug-unit-tested" src="http://www.bryntum.com/wp-content/uploads/2013/02/ug-unit-tested1.png" width="600" /></a></li>
<li><strong>And there&#8217;s more&#8230;</strong>
<p>UberGrid also supports variable row height, column sorting, customizable data rendering, customizable header rendering and more. We&#8217;ll cover these features in a more technical blog post coming soon.</li>
</ul>
<h4>Getting started</h4>
<p>The component provides an API which is very similar to the Ext JS grid panel. UberGrid is a subclass of the <code>Ext.Container</code> class and can participate in any Sencha Touch layout. To create a grid you simply instantiate the <code>UberGrid.Panel</code> class:</p>
<p></p><pre class="crayon-plain-tag">Ext.define('Meeting', {
    extend : 'Ext.data.Model',

    config : {
        fields : ['Id', 'Title', 'StartDate', 'Location']
    }
});

Ext.setup({
    onReady: function () {
        var grid = new UberGrid.Panel({
            margin              : 20,
            rowHeight           : 50,

            columns             : [
                {
                    header      : 'Id',
                    dataIndex   : 'Id',
                    width       : 60,
                    cellCls     : 'id'
                },
                {
                    header      : 'Name',
                    dataIndex   : 'Title',
                    flex        : 1
                },
                {
                    header      : 'Start Date',
                    dataIndex   : 'StartDate',
                    width       : 200
                },
                {
                    header      : 'Location',
                    dataIndex   : 'Location',
                    width       : 250
                }
            ],

            store : new Ext.data.Store({
                autoLoad    : true,
                model       : 'Meeting',
                proxy       : {
                    type    : 'ajax',
                    url     : 'data.js',
                    reader  : { type : 'json' }
                }
            })
        })

        Ext.Viewport.add(grid);
    }
});</pre><p>This example should be pretty self-explanatory. The required configs are <code>columns</code> and <code>store</code>. To dive deeper into the details, please see <a href="/docs/ubergrid/#!/guide/ubergrid_getting_started" target="_blank">the getting started guide</a> in the docs.</p>
<h4>Roadmap</h4>
<p>This is only the initial 1.0.0 release. We&#8217;ll keep adding new features with every new version. Among the nearest ones will be grouping, data index, columns resizing and later tree support. If a particular feature is important for you, let us know and we&#8217;ll try our best to add it!</p>
<h4>Ready to try it out?</h4>
<p>Here are some resources to get you going:</p>
<ul class="list_arrow">
<ul>
<li><a href="/products/ubergrid">Download a trial here</a></li>
<li><a href="/products/ubergrid/examples">Live examples</a> (needs a Webkit browser)</li>
<li><a href="/docs/ubergrid">API docs</a></li>
<li><a href="/docs/ubergrid/#!/guide/ubergrid_getting_started">Getting started guide</a></li>
<li><a href="http://www.sencha.com/learn/touch/">&#8220;Learn Sencha Touch&#8221;</a> at sencha.com</li>
<li><a href="http://www.sencha.com/products/touch/resources/">Additional Sencha Touch resources</a></li>
</ul>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/introducing-ubergrid/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Testing a Sencha Touch application</title>
		<link>http://www.bryntum.com/blog/testing-a-sencha-touch-application/</link>
		<comments>http://www.bryntum.com/blog/testing-a-sencha-touch-application/#comments</comments>
		<pubDate>Thu, 14 Feb 2013 07:59:21 +0000</pubDate>
		<dc:creator>Mats Bryntse</dc:creator>
				<category><![CDATA[Testing]]></category>
		<category><![CDATA[Application Testing]]></category>
		<category><![CDATA[Siesta]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2594</guid>
		<description><![CDATA[<p>I recently joined Arthur Kay for an <a href="http://vimeo.com/58665998">online webinar</a> about writing tests for Sencha applications. As a follow up to the webinar, I thought we should really have a basic sample showing how to write application level tests with Siesta. Note that application tests are very different from unit tests, where you typically take one class and focus on testing its API. For an application test, we typically just ...]]></description>
				<content:encoded><![CDATA[<p>I recently joined Arthur Kay for an <a href="http://vimeo.com/58665998">online webinar</a> about writing tests for Sencha applications. As a follow up to the webinar, I thought we should really have a basic sample showing how to write application level tests with Siesta. Note that application tests are very different from unit tests, where you typically take one class and focus on testing its API. For an application test, we typically just point Siesta to a <code>hostPageUrl</code> and perform actions on the page. The sample in this blog post has been added to the Siesta examples folder. I picked one of the samples in the Sencha Touch 2.1.1 examples folder and used it as my demo app. I also added a login screen, since this is a very common use case in apps built with Sencha Touch. Overall, the demo application is very simple and features these screens:</p>
<p><img style="float:left;width:30%;margin-right:1%" src="http://www.bryntum.com/wp-content/uploads/2013/02/Screen-Shot-2013-02-13-at-5.46.03-PM.png" alt="Screen Shot 2013-02-13 at 5.46.03 PM" width="200" class="alignnone size-thumbnail wp-image-2595" /><img style="float:left;width:30%;margin-right:1%" src="http://www.bryntum.com/wp-content/uploads/2013/02/Screen-Shot-2013-02-13-at-5.47.15-PM.png" alt="Screen Shot 2013-02-13 at 5.47.15 PM" width="200" class="alignnone size-thumbnail wp-image-2596" /><img style="float:left;width:30%;" src="http://www.bryntum.com/wp-content/uploads/2013/02/Screen-Shot-2013-02-13-at-5.47.25-PM.png" alt="Screen Shot 2013-02-13 at 5.47.25 PM" width="200" class="alignnone size-thumbnail wp-image-2597" /></p>
<div style="clear:both"></div>
<p></p>
<p>First, a user has to login and after logging in, an address list is shown. You can then click a contact in the list and see his or her details. To test this app, I wrote three basic application tests.</p>
<p>1. A sanity test to assert that upon visiting the app page, the login screen is shown<br />
2. A basic login test, which navigates to one of the contacts in the list.<br />
3. A basic logout test which makes sure we can log out ok.</p>
<p>The Harness code can be seen below:</p>
<p></p><pre class="crayon-plain-tag">var Harness = Siesta.Harness.Browser.SenchaTouch;

Harness.configure({
    title     : 'Sencha Touch 2 samples'
});

Harness.start(
    {
        group       : 'Application tests',
        hostPageUrl : 'DemoApp/',
        preload     : [],
        performSetup : false,       // This is done by the app itself
        items       : [
            'application_tests/100_sanity.t.js',
            'application_tests/101_login.t.js',
            'application_tests/102_logout.t.js'
        ]
    }
);
// eof Harness.start</pre><p></p>
<h5>1. The &#8216;sanity&#8217; test</h5>
<p>A sanity test is a just a nice simple way of knowing the health of a test subject. For a typical application, that might mean to load up the start page and assure the login window can be seen. No errors should be found, and no globals leaked etc. Here&#8217;s the Siesta test code for the &#8216;sanity&#8217; test.</p>
<p></p><pre class="crayon-plain-tag">StartTest(function(t) {

    t.chain(
        { waitFor : 'CQ', args : 'loginview' },
        
        function(next) {
            t.pass('Should find login view on app start');
            t.ok(t.cq1('#logInButton'), 'Should find a login button');
        }
    );
});</pre><p></p>
<p>The test waits for a known <code>Ext.ComponentQuery</code>, the &#8216;loginview&#8217; xtype. Once it&#8217;s detected, we consider that a green light and just check that the login button is also created. This is very simplistic, but still tells us that the landing page is healthy.</p>
<h5>2. The &#8216;login&#8217; test</h5>
<p>This test starts by waiting for the login button to be visible. Since we want to tap<br />
the button, it&#8217;s not enough just to wait for it to exist &#8211; it also has to be visible so we can &#8216;reach&#8217; it (during rendering, components are often rendered off-screen). After this step, we simply set some values in the login fields. We&#8217;re not simulating typing, since there is no reason to test this &#8211; it&#8217;s a lot faster and more robust to just use the public Field API. After logging in, we wait for a <code>CompositeQuery</code> &#8211; a component query combined with a CSS query. Once we find a contact item in the DOM, we tap it to show the contact details. </p>
<p></p><pre class="crayon-plain-tag">StartTest(function(t) {

    t.chain(
        { waitFor : 'componentVisible', args : '#logInButton' },

        function(next) {
            t.cq1('#userNameField').setValue('John Doe');
            t.cq1('#passwordField').setValue('SecretUnhackablePW');
            next();
        },

        { action : 'tap', target : '>> #logInButton' },

        // We'd like to find a headshot icon the DOM, that's proof the main app has been launched ok
        { waitFor : 'compositeQuery', args : 'contacts => .headshot' },

        function(next) {
            t.pass('Should be able login login and see contact list');
            next();
        },

        { action : 'tap', target : 'contacts => .headshot' },

        { waitFor : 'componentVisible', args : 'map' },

        function(next) {
            t.pass('Should see a detail view with map after tapping a contact');
        }
    );
});</pre><p></p>
<h5>3. The &#8216;logout&#8217; test</h5>
<p>This logout test just logs in, and immediately logs out again. This should take the user back to the login dialog, and this is exactly what&#8217;s being assured in the test code below.</p>
<p></p><pre class="crayon-plain-tag">StartTest(function(t) {

    t.chain(
        { waitFor : 'componentVisible', args : '#logInButton' },

        function(next) {
            t.cq1('#userNameField').setValue('John Doe');
            t.cq1('#passwordField').setValue('SecretUnhackablePW');
            next();
        },

        { action : 'tap', target : '>> #logInButton' },

        // We'd like to find a headshot icon the DOM, that's proof the main app has been launched ok
        { waitFor : 'compositeQuery', args : 'contacts => .headshot' },

        function(next) {
            t.willFireNTimes(App, 'logout', 1);
            next();
        },

        { action : 'tap', target : '>>#logoutButton' },

        { waitFor : 'componentVisible', args : 'loginview' },

        function(next) {
            t.pass('Should be able to log out and see login view');
        }
    );
});</pre><p></p>
<h5>Refactoring</h5>
<p>As you can see in the logout test, we&#8217;re duplicating the login process code. If we want to perform a login in many tests, it makes sense to break this behavior out to its own Test method. You do this by creating your own Test class.</p>
<p></p><pre class="crayon-plain-tag">Class('Your.Test.Class', {

    isa     : Siesta.Test.SenchaTouch,
    
    methods: {
        login : function(user, pw, next){
            var me = this;

            this.chain(
                { waitFor : 'componentVisible', args : '#logInButton' },

                function(next) {
                    me.cq1('#userNameField').setValue(user);
                    me.cq1('#passwordField').setValue(pw);
                    next();
                },

                { action : 'tap', target : '>> #logInButton' },
                { waitFor : 'compositeQuery', args : 'contacts => .headshot' },
                
                next  
            );
        }
    }
});</pre><p></p>
<p>To plug this test class into your Siesta test suite, you simply configure the <code><a href="http://www.bryntum.com/docs/siesta/#!/api/Siesta.Harness-cfg-testClass">testClass</a></code> property of the harness.</p>
<p>I hope this gives you some inspiration on how to write application level tests for your Sencha Touch applications. Don&#8217;t forget to write unit tests too <img src='http://www.bryntum.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/testing-a-sencha-touch-application/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Under the hood of the Socket.IO + Express example</title>
		<link>http://www.bryntum.com/blog/under-the-hood-of-the-socket-io-express-example/</link>
		<comments>http://www.bryntum.com/blog/under-the-hood-of-the-socket-io-express-example/#comments</comments>
		<pubDate>Mon, 11 Feb 2013 06:32:09 +0000</pubDate>
		<dc:creator>Mats Bryntse</dc:creator>
				<category><![CDATA[Ext Scheduler]]></category>
		<category><![CDATA[NodeJS]]></category>
		<category><![CDATA[Socket.IO]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2539</guid>
		<description><![CDATA[<p></p> <p>You might have seen one of the sneak preview videos on YouTube about a collaborative demo app using our scheduler with Ext JS and Sencha Touch. With our recent release of the first 2.2 beta version of Ext Scheduler, it&#8217;s now time to look under the hood of this demo application. When we were about to release the Touch Scheduler, we thought we should really put it to the ...]]></description>
				<content:encoded><![CDATA[<p><iframe width="560" height="315" src="http://www.youtube.com/embed/D26GJTbgc5o" frameborder="0" style="border:1px solid #666" allowfullscreen></iframe></p>
<p>You might have seen one of the sneak preview videos on YouTube about a collaborative demo app using our scheduler with Ext JS and Sencha Touch. With our recent release of the first 2.2 beta version of Ext Scheduler, it&#8217;s now time to look under the hood of this demo application. When we were about to release the Touch Scheduler, we thought we should really put it to the test before releasing it. A few days later, we had written a collaborative app where changes are being broadcasted immediately when a task is modified. The live aspect of the application is not something you see often in web apps today but with tools like Express and Socket.IO it&#8217;s now very easy to accomplish. This post is quite long and contains lots of code, but here&#8217;s how we did it:</p>
<p><strong>Setting up an Express web server</strong></p>
<p>First of all, we had to setup a simple Express web server which runs on Node (you need at least Node 0.6+). <a href="http://nodejs.org/">Download Node.js here</a>. We also need socket.io which you can find <a href="http://socket.io/">here</a>. To avoid having to involve a database on the server, we simply created an in-memory array &#8220;DB&#8221; of tasks to serve as the database. It also has add/remove/update methods to support all CRUD operations. Below is the script which launches a simple Express instance on port 3000. </p>
<p><strong>node_backend.js</strong></p><pre class="crayon-plain-tag">///Module dependencies.
var http = require('http'),
    express = require('express'),
    app = module.exports = express.createServer(),
    fs = require('fs'),
    io = require('socket.io').listen(app);


//Server Configuration
app.configure(function(){
    app.use(express.bodyParser());
    app.use(express.methodOverride());
    app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.get('/', function(req, res){
    res.sendfile(__dirname+'/public/index.html');
});

app.listen(3000);


//'DATABASE'
var DB = {
    eventsTable: [
        {Id: 1, ResourceId: 2, Name : 'Chase turkey', StartDate : "2010-12-09 08:00", EndDate : "2010-12-09 10:00", Done : true},
        {Id: 2, ResourceId: 1, Name : 'Stuff turkey', StartDate : "2010-12-09 10:00", EndDate : "2010-12-09 12:00", Done : true},
        {Id: 3, ResourceId: 3, Name : 'Cook turkey', StartDate : "2010-12-09 12:00", EndDate : "2010-12-09 15:00", Done : true},
        {Id: 4, ResourceId: 5, Name : 'Set table', StartDate : "2010-12-09 14:00", EndDate : "2010-12-09 16:00", Done : false},
        {Id: 5, ResourceId: 4, Name : 'Serve dinner', StartDate : "2010-12-09 16:00", EndDate : "2010-12-09 19:00", Done : false},
        {Id: 6, ResourceId: 6, Name : 'Hack on NodeJS', StartDate : "2010-12-09 16:00", EndDate : "2010-12-09 18:30", Done : false},
        {Id: 7, ResourceId: 7, Name : 'Clean up', StartDate : "2010-12-09 19:00", EndDate : "2010-12-09 20:00", Done : false},
        {Id: 8, ResourceId: 8, Name : 'Do laundry', StartDate : "2010-12-09 17:00", EndDate : "2010-12-09 19:00", Done : false}
    ],

    getEventsData: function(){
        return this.eventsTable;
    },

    // get record by ID
    getById: function(id){
        var table = this.getEventsData(),
            current;

        for(var i=0, l=table.length; i<l; i+=1){
            current = table[i];

            if(current.Id === id){
                return current;
            }
        }
        return null;
    },

    // update record
    update: function(record){
        var data       = record.data;
        var item       = this.getById(data.Id);

        for(var f in data){
            item[f] = data[f];
        }
    },

    // add records
    add: function(records){
        var table = this.getEventsData(),
            record,
            ID;

        for(var i=0, l=records.length; i<l; i+=1){
            record     = records[i];
            ID         = table[table.length-1].Id + 1;

            record.data.Id = ID;
            table.push(record.data);
        }
    },

    // remove records
    remove: function(ids){
        var table = this.getEventsData();

        ids.forEach(function(id) {
            item       = this.getById(id);
            table.splice(table.indexOf(item), 1);
        }, this);
    }
};</pre><p></p>
<p>We then created a basic HTML <code>index.html</code> page which includes Ext JS, Ext Scheduler, Socket.IO and our application JS file (in the example there is also a touch version including Sencha Touch instead). Contents below:</p>
<p><strong>index.html</strong></p><pre class="crayon-plain-tag"><!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

    <!--Ext and ux styles -->
    <link href="http://localhost/extjs-4.2.0.265/resources/css/ext-all.css" rel="stylesheet" type="text/css"/>

    <!--Scheduler styles-->
    <link href="http://localhost/ExtScheduler3.x/resources/css/sch-all-debug.css" rel="stylesheet" type="text/css"/>

    <!--Example styles -->
    <link href="/css/style.css" rel="stylesheet" type="text/css"/>

    <!-- Socket IO-->
    <script src="/socket.io/socket.io.js"></script>

    <!--Ext 4 includes-->
    <script src="http://localhost/extjs-4.2.0.265/ext-all-debug.js" type="text/javascript"></script>

    <!--Scheduler files-->
    <script src="http://localhost/ExtScheduler3.x/sch-all-debug.js" type="text/javascript"></script>

    <!--Application files-->
    <script src="/app.js" type="text/javascript"></script>

    <title>Node/Express/Socket.IO demo</title>
</head>
<body>
</body>
</html></pre><p></p>
<p>Now to the application bootstrap part, which means adding an app.js which should work for both Ext JS and Sencha Touch. This required a little bit of normalization code but nothing really difficult.</p>
<p><strong>app.js</strong></p><pre class="crayon-plain-tag">Ext.Loader.setConfig({
    enabled         : true,
    disableCaching  : true
});

Ext.require([
    'App.view.SchedulerGrid'
]);

// Limit the resolution, to avoid putting too much data on the wire.
Sch.preset.Manager.getPreset('hourAndDay').timeResolution.increment = 15;

Ext.application({
    name : 'App',

    viewport : {
        layout : {
            type : 'vbox',
            align : 'stretch'
        }
    },

    // Initialize application
    launch : function() {
        var field = new Ext.form.Text({
            fieldLabel : 'User ',
            height : 30,
            label : 'User ',
            value : 'John Doe',
            labelWidth: 115,
            listeners : {
                change : function(field, value) {
                    scheduler.userName = value;
                }
            }
        });

        var scheduler = Ext.create('App.view.SchedulerGrid', {
            title               : 'Node + socket.io example',
            startDate           : new Date(2010, 11, 9, 8),
            endDate             : new Date(2010, 11, 9, 20),
            flex                : 1,
            userName            : field.getValue()
        });

        var vp;

        if (Ext.versions.touch) {
            vp = Ext.Viewport;
        } else {
            // Ext JS
            vp = new Ext.Viewport(this.viewport);

            // Uncomment this to see what's happening in the EventStore
            //Ext.util.Observable.capture(scheduler.eventStore, function() { console.log(arguments); });

            scheduler.on('eventcontextmenu', this.onEventContextMenu, this);
            Ext.QuickTips.init();
        }

        vp.add([
            new Ext.form.Panel({
                region: 'north',
                hidden : Ext.os && Ext.os.is.phone,
                padding: 5,
                border : false,
                height : 55,
                items : field
            }),
            scheduler
        ]);
    },

    onEventContextMenu : function(scheduler, rec, e) {
        e.stopEvent();
        
        if (!scheduler.gCtx) {
            scheduler.gCtx = new Ext.menu.Menu({
                items : [
                    {
                        text : 'Delete event',
                        iconCls : 'icon-delete',
                        handler : function() {
                            scheduler.eventStore.remove(scheduler.gCtx.rec);
                        }
                    }  
                ]
            });
        }
        scheduler.gCtx.rec = rec;
        scheduler.gCtx.showAt(e.getXY());
    }    
});</pre><p></p>
<p>This creates a simple <code>Viewport</code> and puts a scheduler inside it together with a form where you can enter your name. Let&#8217;s move on to take a peek at the <code>App.view.SchedulerGrid</code> class. </p>
<h5>UI classes</h5>
<p>As with the app.js file, we had to make sure this class could be used regardless of the underlying Sencha framework. For this view class, we had to use the <code>constructor</code> instead of relying on the <code>initComponent</code> hook since this hook doesn&#8217;t exist in Sencha Touch (instead there is an <code>init</code> hook). By relying on the constructor, we saved us a bit of normalization code.</p>
<p><strong>App.view.SchedulerGrid</strong></p><pre class="crayon-plain-tag">Ext.define('App.view.SchedulerGrid', {
    extend              : 'Sch.panel.SchedulerGrid',

    requires            : ['App.store.EventStore', 'App.store.ResourceStore'],

    userName            : null,
    draggingRecord      : null,
    socketHost          : null,
    rowHeight           : 75,
    barMargin           : 10,
    eventBarTextField   : 'Name',
    viewPreset          : 'hourAndDay',
    eventBodyTemplate   : '<div>{Name}</div><div class="blocked-by {[values.Blocked ? \"\" : \"x-hidden\"]}">{BlockedBy}</div>',

    constructor         : function() {
        var me = this;
        //create a WebSocket and connect to the server running at host domain
        var socket = me.socket = io.connect(me.socketHost);

        Ext.apply(me, {

            viewConfig : {
                onEventUpdate: function (store, model, operation) {
                    // Skip local paints of the record currently being dragged
                    if (model !== me.draggingRecord) {
                        this.horizontal.onEventUpdate(store, model, operation);
                    }
                }
            },

            columns : [
                { header : 'Name', width : 120, dataIndex : 'Name', sortable : true}
            ],

            eventRenderer : function(event, resource, tplData) {
                tplData.cls = '';

                if (event.data.Done) {
                    tplData.cls += ' sch-event-done ';
                }

                if (event.data.Blocked) {
                    tplData.cls += ' sch-event-blocked ';

                    if (event === me.draggingRecord) {
                        tplData.cls += ' x-hidden ';
                    }
                };
                return event.data;
            },

            resourceStore : new App.store.ResourceStore({ /* Extra configs here */}),

            eventStore : new App.store.EventStore({
                socket : socket
            })
        });

        this.callParent(arguments);

        // Change default drag drop behavior to update the dragged record 'live'
        me.on({
            eventdragstart          : me.onDragStart,
            eventdrag               : me.onEventDrag,

            aftereventdrop          : me.onDragEnd,
            scope                   : me
        });
    },

    onEventCreated : function(record) {
        record.set('Name', 'New task');
    },

    // Block a record when it is being dragged
    onDragStart : function(view, records) {

        var rec = records[0];
        this.draggingRecord = rec;

        rec.block(this.userName);
    },

    // Update underlying record as it is moved around in the schedule
    onEventDrag : function(sch, draggedRecords, startDate, newResource) {

        if (newResource && startDate) {
            var task = draggedRecords[0];
            task.beginEdit();
            task.setStartDate(startDate, true);
            task.assign(newResource);
            task.endEdit();
        }
    },

    // Unblock a record after dragging it
    onDragEnd : function(view, records) {
        this.draggingRecord = null;

        records[0].unblock();
    }
});</pre><p></p>
<p>There are basically just two special things about the scheduler above: When a drag operation starts, the dragged Model is marked as <em>blocked</em> and as the cursor is moved, the model is continuously updated until the drag operation is finished. Since we don&#8217;t want our own &#8216;local&#8217; view to repaint itself on such model updates, we override the onEventUpdate method to prevent it. </p>
<h5>Data classes</h5>
<p>So far we have only presented the UI and the application classes. Let&#8217;s move on to where the real magic happens. First, let&#8217;s look at the underlying <code>App.model.CustomEvent</code>, its sources can be seen below. To have this class support both Sencha frameworks, we needed a bit of normalization code since ST uses its config system to define class members. </p>
<p><strong>App.model.CustomEvent</strong></p><pre class="crayon-plain-tag">(function() {
    var fields = [
        { name  : 'Blocked' },
        { name  : 'BlockedBy' },
        { name  : 'Done', type : 'boolean'}
    ];

    Ext.define('App.model.CustomEvent', {
        extend      : 'Sch.model.Event',
        fields      : fields,

        // Sencha Touch
        config      : {
            fields  : fields
        },

        block       : function(userName) {
            this.set({
                Blocked     : true,
                BlockedBy   : userName
            })
        },

        unblock     : function() {
            this.set({
                Blocked     : false,
                BlockedBy   : null
            });
        }
    });
}());</pre><p></p>
<p>This model is consumed by the <code>App.store.EventStore</code> which is just a plain store consuming a special mixin we wrote. It defines the model to use and also initializes the socket its bound to.</p>
<p><strong>App.store.EventStore</strong></p><pre class="crayon-plain-tag">Ext.define('App.store.EventStore', {
    extend : "Sch.data.EventStore",

    requires  : [
        'App.model.CustomEvent'
    ],

    config : Ext.versions.touch ? {
        socket  : null,
        model   : 'App.model.CustomEvent'
    } : null,

    model   : 'App.model.CustomEvent',
    mixins  : [
        'App.store.mixin.SocketIO'
    ],

    proxy: {
        type: 'memory',
        reader: {
            type: 'json'
        }
    },

    constructor : function() {
        this.callParent(arguments);
        this.initSocket();
    }
});</pre><p></p>
<p><strong>Socket.IO &#8211; client side</strong></p>
<p>If you have worked with regular Sencha data stores before, you&#8217;ll note we are doing things a bit different here. We&#8217;re not using any of the load/save capabilities of the data package. We&#8217;re instead letting socket.io handle all the CRUD traffic to and from the server. The contract for this mixin class can be summarized as this statement: </p>
<blockquote><p>Observe the socket to know what others are doing, and let the others know what I&#8217;m doing
</p></blockquote>
<p>The socket API allows you to observe it using the <code>on</code> method, and you can use the <code>emit</code> to broadcast local changes.</p>
<p><strong>App.store.mixin.SocketIO</strong></p><pre class="crayon-plain-tag">Ext.define('App.store.mixin.SocketIO', {
    socket : null,

    getSocket : function () {
        return this.socket;
    },

    initSocket : function () {

        var that = this;
        var socket = this.getSocket();

        socket.on('server-doInitialLoad', function (data) {
            that.onInitialLoad(data);
        });
        socket.on('server-doUpdate', function (data) {
            that.onRemoteUpdate(data);
        });
        socket.on('server-doAdd', function (data) {
            that.onRemoteAdd(data);
        });
        socket.on('server-syncId', function (data) {
            that.onRemoteSyncId(data);
        });
        socket.on('server-doRemove', function (data) {
            that.onRemoteRemove(data);
        });

        this.myListeners = {
            add             : this.onLocalAdd,
            update          : this.onLocalUpdate,
            remove          : this.onLocalRemove,
            addrecords      : this.onLocalAdd,
            updaterecord    : this.onLocalUpdate,
            removerecords   : this.onLocalRemove
        };

        this.addMyListeners();

        //Load initial data to Store from Server
        this.doInitialLoad();
    },

    addMyListeners : function () {
        //Add event listeners to store operations
        this.on(this.myListeners);
    },

    removeMyListeners : function () {
        //Add event listeners to store operations
        this.un(this.myListeners);
    },

    /**
     * Emit event to server in order to receive initial data for store from the DB.
     */
    doInitialLoad : function () {
        this.getSocket().emit('client-doInitialLoad');
    },

    /* BEGIN REMOTE LISTENER METHODS */

    /**
     * New records were added remotely, add them to our local client store
     */
    onRemoteAdd : function (data) {
        var records = data.records,
            record,
            current,
            model = this.getModel();

        this.removeMyListeners();

        for (var i = 0, l = records.length; i < l; i += 1) {
            current = records[i].data;

            //change dates from JSON form to Date
            current.startDate = new Date(current.StartDate);
            current.endDate = new Date(current.EndDate);

            // Work around a bug in ST when adding new records (internalId not set correctly)
            this.add(new model(current, current.Id));
        }

        this.addMyListeners();
    },

    onRemoteSyncId : function (data) {
        var records = data.records,
            model = this.getModel();

        this.removeMyListeners();

        Ext.Array.each(records, function(updatedRecord) {
            var internalId = updatedRecord.internalId;

            this.each(function (rec, idx) {
                if (rec.internalId == internalId) {
                    this.remove(rec);
                    this.add(new model(updatedRecord.data, updatedRecord.data.Id))
                    return false;
                }
            }, this);
        }, this);

        this.addMyListeners();
    },

    /**
     * Records were updated remotely, update them in our local client store
     */
    onRemoteUpdate : function (data) {
        var localRecord;

        // Only one record updated at a time
        data = data.record.data;

        this.removeMyListeners();

        localRecord = this.getById(data.Id);
        if (localRecord) {
            data.StartDate && (data.StartDate = new Date(data.StartDate));
            data.EndDate && (data.EndDate = new Date(data.EndDate));

            localRecord.set(data);
        }

        this.addMyListeners();
    },

    /**
     * Records were removed remotely, remove them from our local client store
     */
    onRemoteRemove : function (data) {
        var ids = data.data,
            record,
            current;

        this.removeMyListeners();

        for (var i = 0, l = ids.length; i < l; i += 1) {
            current = ids[i];
            record = this.getById(current);

            this.remove(record);
        }

        this.addMyListeners();
    },

    /**
     * Initial data loaded from server.
     */
    onInitialLoad : function (data) {
        var data = data.data;
        (this.loadData || this.setData).call(this, data);
    },

    /* EOF REMOTE LISTENER METHODS */


    /* BEGIN LOCAL STORE LISTENER METHODS */

    /**
     * On adding records to client store, send event to server and add items to DB.
     */
    onLocalAdd : function (store, records, index, opts) {
        var recordsData = [];
        records = records.length ? records : [records];

        for (var i = 0, l = records.length; i < l; i += 1) {
            records[i].data.Name = 'New Assignment';

            recordsData.push({
                data : records[i].data,
                internalId : records[i].internalId
            });
        }

        this.getSocket().emit('client-doAdd', { records : recordsData });
    },

    /**
     * On updating records in client store, send event to server and update items in DB.
     */
    onLocalUpdate : function (store, record) {
        var data = { Id : record.getId() };

        for (var prop in record.previous) {
            data[prop] = record.data[prop];
        }

        this.getSocket().emit('client-doUpdate', { record : { data : data } });
    },

    /**
     * On adding removing records from client store, send event to server and remove items from DB.
     */
    onLocalRemove : function (store, records, index, opts) {
        records = records.length ? records : [records];
        var ids = Ext.Array.map(records, function (rec) {
            return rec.get('Id');
        });

        this.getSocket().emit('client-doRemove', { ids : ids });
    }

    /* EOF LOCAL STORE LISTENER METHODS */
});</pre><p></p>
<p>If you read the code above, you&#8217;ll also notice a few framework normalization snippets, but all in all it&#8217;s a trivial data mixin class. The final piece of this application is the socket.io integration on the server side. </p>
<p><strong>Socket.IO &#8211; server side</strong></p>
<p>This is a quite straight forward piece of code. Once the socket connection is available we observe it for actions invoked on the client side (load/update/add/remove). Using the <code>emit</code> method of the <code>broadcast</code> property of the socket, we can reach all connected clients. </p>
<p><strong>node_backend.js</strong></p><pre class="crayon-plain-tag">// WEBSOCKETS COMMUNICATION
io.sockets.on('connection', function (socket) {

    //Load initial data to client Store
    socket.on('client-doInitialLoad', function(data){
        socket.emit('server-doInitialLoad', { data : DB.getEventsData()});
    });

    //Update records in DB and inform other clients about the change
    socket.on('client-doUpdate', function(data){
        var record   = data.record;

        DB.update(record);

        socket.broadcast.emit('server-doUpdate', data);
    });

    //Add record to DB and inform other clients about the change
    socket.on('client-doAdd', function(data){
        var records   = data.records;

        DB.add(records);

        socket.broadcast.emit('server-doAdd', { records : records });

        //Sync ID of new record with client Store
        socket.emit('server-syncId', { records : records });
    });

    //Remove record from DB and inform other clients about the change
    socket.on('client-doRemove', function(data){
        var ids   = data.ids;

        DB.remove(ids);

        socket.broadcast.emit('server-doRemove', { data : data.ids });
    });
});</pre><p></p>
<p><strong>Styling</strong></p>
<p>Finally, to visualize that someone else is moving a task bar on your screen, we placed a hand cursor for tasks that are in the <em>&#8216;Blocked&#8217;</em> state. Next to the cursor, we also show the name of the user moving the task. This gives any observing users an extra clue about what is going on.</p>
<p><img src="http://www.bryntum.com/wp-content/uploads/2013/02/cursor.png" alt="cursor" width=200 class="alignnone size-thumbnail wp-image-2554" /></p>
<p><strong>Summing up&#8230;</strong></p>
<p>By combining our products with Ext JS, Sencha Touch, Express and Socket.IO we were able to write a cool collaborative application with realtime updates. We were also able to reuse the application code for both Sencha frameworks with only a very tiny amount of normalization code. If you like this example and find it useful (or if you have suggestions of how it can be improved), please let us know in the comments. To try it out, you need to download the Ext Scheduler 2.2 version and look in the &#8216;/examples/nodejs&#8217; folder. There is an &#8216;index.html&#8217; file for desktop and an &#8216;index_touch.html&#8217; for touch devices.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/under-the-hood-of-the-socket-io-express-example/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Road to Ext Scheduler 2.2: Upgrade guide</title>
		<link>http://www.bryntum.com/blog/road-to-ext-scheduler-2-2-upgrade-guide/</link>
		<comments>http://www.bryntum.com/blog/road-to-ext-scheduler-2-2-upgrade-guide/#comments</comments>
		<pubDate>Fri, 08 Feb 2013 14:45:04 +0000</pubDate>
		<dc:creator>Mats Bryntse</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Ext Scheduler]]></category>
		<category><![CDATA[2.2]]></category>
		<category><![CDATA[Upgrade]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2517</guid>
		<description><![CDATA[<p>We just released the first 2.2 beta version in the CustomerZone. This release has quite a few changes due to some major refactorings mentioned in my <a href="http://www.bryntum.com/blog/road-to-ext-scheduler-v2-2-overrides/">previous post</a>. To sum up, we adapted a lot of the code to be shared with our Sencha Touch version and we have also tried hard to eliminate as many private Ext JS overrides as possible. A few of the changes in this ...]]></description>
				<content:encoded><![CDATA[<p>We just released the first 2.2 beta version in the CustomerZone. This release has quite a few changes due to some major refactorings mentioned in my <a href="http://www.bryntum.com/blog/road-to-ext-scheduler-v2-2-overrides/">previous post</a>. To sum up, we adapted a lot of the code to be shared with our Sencha Touch version and we have also tried hard to eliminate as many private Ext JS overrides as possible. A few of the changes in this release may break backwards compatibility, depending on how you use our components. Here is the API Changes section of the 2.2-beta-1 changelog:</p>
<div style="padding:15px; font:12px 'Bitstream Vera Sans Mono','Courier',monospace !important; background: #f8f8f8">
[API CHANGES]<br />
    &#8211; BREAKING: forceFit now defined on Panel, it was previously on the View. Compatibility patch installed.<br />
    &#8211; BREAKING: Removed Sch.util.HeaderRenderers class (should no longer be required).<br />
    &#8211; BREAKING: Removed Sch.view.Locking class.<br />
    &#8211; BREAKING: Inner panel is no longer an instance of our Sch.panel.SchedulerXXX classes, it is whatever Ext JS decides<br />
     (normally a regular Ext.grid.Panel)<br />
    &#8211; &#8216;aftereventdrop&#8217; now includes the dragged records as a new argument<br />
    &#8211; Removed &#8216;useDragProxy&#8217; option of Sch.feature.DragDrop<br />
    &#8211; Removed &#8216;Sch.feature.DragZone&#8217; class
</div>
</p>
<p>Let&#8217;s walk through this list, one by one! </p>
<p><strong>forceFit now defined on Panel</strong></p>
<p>This one is pretty self explanatory, the <code>forceFit</code> config option which makes sure all the time columns fit inside the viewport is now defined on the panel level, instead of the view. </p>
<p></p><pre class="crayon-plain-tag">// v2.1 :
new SchedulerPanel({
    viewConfig : { forceFit : true },
    ...
});

// v2.2 :
new SchedulerPanel({
    forceFit : true,
    ...
});</pre><p></p>
<p>For the sake of backwards compatibility, we look for the flag on the <code>viewConfig</code>, so this change isn&#8217;t really breaking but you should still change your code if you use this config option.</p>
<p><strong>Removed Sch.util.HeaderRenderers</strong></p>
<p>This class was introduced as a performance booster in a previous version when every time column was implemented as its own <code>Ext.grid.Column</code>. We now only use one <code>Ext.grid.Column</code> for the entire schedule, meaning there is no overhead in having many cells as it previously was and the need for this class is gone. If you still require it for some reason, you can still keep the copy from the 2.1 release, though it&#8217;s no longer supported officially.</p>
<p><strong>Removed Sch.view.Locking class</strong></p>
<p>This class previously subclassed the <code>Ext.grid.LockingView</code> and relayed events from the inner &#8216;normal&#8217; view to the top view. From the top view, it was then re-relayed to the top panel. In 2.2, the scheduler panel relays the SchedulerView events directly and there is no longer any need to override this private Ext JS class.</p>
<p><strong>Inner panel is no longer an instance of our Sch.panel.SchedulerXXX classes</strong></p>
<p>This is a quite major change, but makes it a lot easier for us maintain our product. In 2.1 we were overriding internals of the GridPanel which is never a good thing. We no longer use our own panel class for any of the child grids. In 2.1, the SchedulerPanel consisted of a top SchedulerPanel, with a <em>locked</em> GridPanel + a <em>normal</em> SchedulerPanel (which had a SchedulerView). In 2.2, there is a main SchedulerPanel, with a locked GridPanel and a normal GridPanel. The normal grid panel is using a SchedulerView. This could potentially be an issue for you if you have written your own plugins which assumes that the previously nested SchedulerPanel existed. An example:</p>
<p></p><pre class="crayon-plain-tag">Ext.define("MyEditor", {
    extend      : "Ext.form.FormPanel",

    lockableScope : 'normal',
    
    init : function (grid) {
        
        // => ERROR! getSchedulingView is not a property of Ext.grid.Panel
        this.schedulerView  = grid.getSchedulingView();

        this.schedulerView.on({
            afterrender     : this.onSchedulerRender,
            destroy         : this.onSchedulerDestroy,
            dragcreateend   : this.onDragCreateEnd,
            
            scope           : this
        });

        ...
    },</pre><p></p>
<p>For sake of backwards compatibility, we added an inline override to make sure the &#8216;getScheduling&#8217; method exists on the GridView, but you should not rely on its existence (you might get other similar errors about missing methods on the GridPanel). To update your code and fix the issue above, you have two options.</p>
<p>1. Register the plugin in the &#8216;top&#8217; panel scope.</p>
<p></p><pre class="crayon-plain-tag">Ext.define("MyEditor", {
    extend      : "Ext.form.FormPanel",

    lockableScope : 'top',  // Attach this plugin the 'top' panel which is the SchedulerPanel
    
    init : function (grid) {
        
        // => Works! getSchedulingView is a method of the SchedulerPanel
        this.schedulerView  = grid.getSchedulingView();

        ...
    },</pre><p></p>
<p>2. Change to use &#8216;getView&#8217; instead.</p>
<p></p><pre class="crayon-plain-tag">Ext.define("MyEditor", {
    extend      : "Ext.form.FormPanel",

    lockableScope : 'normal',

    init : function (grid) {
        
        // => Works! getView is a method of the GridPanel
        this.schedulerView  = grid.getView();

        ...
    },</pre><p></p>
<p><strong>Removed &#8216;useDragProxy&#8217; option of Sch.feature.DragDrop</strong></p>
<p>This option was introduced in the early days before we started using <code>document.elementFromPoint</code> for the drag drop implementation. All browsers support this reliably and this option should no longer be required.</p>
<p><strong>Removed &#8216;Sch.feature.DragZone&#8217; class</strong></p>
<p>This class was introduced in the early days before we started using <code>document.elementFromPoint</code> for the drag drop implementation. If you rely on this class, you can still keep the source code from the previous version and maintain it &#8211; though it is no longer supported by us. </p>
<h4>Please give us feedback</h4>
<p>The refactorings we made for 2.2 will make a huge difference as we support newer Ext JS releases. We&#8217;re no longer overriding lots of private Ext JS internals and we have new unit tests to prove it. We have listed the breaking changes above and we hope we did not miss anything, if we did please let us know as soon as possible. We&#8217;re eager to hear about your experience and to get your feedback after you have upgraded to the new version. If you stumble upon any issues, please let us know by starting a thread in our <a href="http://bryntum.com/forum/">forums</a>. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/road-to-ext-scheduler-2-2-upgrade-guide/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Road to Ext Scheduler v2.2: Overrides</title>
		<link>http://www.bryntum.com/blog/road-to-ext-scheduler-v2-2-overrides/</link>
		<comments>http://www.bryntum.com/blog/road-to-ext-scheduler-v2-2-overrides/#comments</comments>
		<pubDate>Thu, 31 Jan 2013 15:38:14 +0000</pubDate>
		<dc:creator>Mats Bryntse</dc:creator>
				<category><![CDATA[Ext Scheduler]]></category>
		<category><![CDATA[Overrides]]></category>
		<category><![CDATA[Siesta]]></category>
		<category><![CDATA[Testing]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2485</guid>
		<description><![CDATA[<p>We&#8217;re currently busy finalizing a major refactoring of our Ext Scheduler component which will be released as v2.2. There are two major reasons for this refactoring. First of all we needed to refactor to enable certain pieces of the core functionality to be shared with our Touch Scheduler. These bits of code relate to non-UI parts of the component, such as stores, models, utility classes etc. The second reason for ...]]></description>
				<content:encoded><![CDATA[<p>We&#8217;re currently busy finalizing a major refactoring of our Ext Scheduler component which will be released as v2.2. There are two major reasons for this refactoring. First of all we needed to refactor to enable certain pieces of the core functionality to be shared with our Touch Scheduler. These bits of code relate to non-UI parts of the component, such as stores, models, utility classes etc. The second reason for the refactoring, was to future proof the component a bit and clean up as many Ext JS overrides as possible. With the recent Ext JS 4.2 beta releases, too many things broke that should not break. </p>
<h4>Global Ext JS overrides</h4>
<p>In the latest official version of Ext Scheduler, things are pretty much under control. There&#8217;s a large unit test suite covering loads of scenarios and the component is very stable. For example, we&#8217;re testing that no global variables are leaked. We also scan the entire Ext JS source tree to find any global overrides of the Ext JS library (a bad practice we used in older versions). </p>
<p></p><pre class="crayon-plain-tag">// Our Siesta suite will detect this and fail
Ext.grid.View.override({
   refresh : function() {
       // Do something else
   }
});</pre><p></p>
<p>We don&#8217;t write code like this anymore. Override code like this introduces a lot of uncertainty and is just as bad as regular global variables. As a component maker, we should not make any changes to our &#8216;surroundings&#8217;. Thanks to our zero-tolerance test, any attempt to make a global Ext JS override will now break our build. You can check how the test is performed in our test named <strong>/tests/sanity/012_no_overrides.t.js </strong>.</p>
<h4>Overriding private Ext JS methods</h4>
<p>Another aspect of overrides is when you override a private method in a subclass (see code below). When faced with a tricky problem, this can be tempting since there is nothing stopping you in the world of javascript and Ext JS. But overriding non-public and non-documented methods will likely lead to problems, maybe not tomorrow &#8211; but at some point. It happened to us quite frequently with each new version of Ext JS we had to support. At too many places we had simply overwritten private Ext JS methods, whose implementations then changed slightly in newer releases. </p>
<p></p><pre class="crayon-plain-tag">// Overriding a private method of the superclass
Ext.define("Sch.mixin.TimelineGridView", {
    extend : 'Ext.grid.View',

    // We don't want Ext guessing if this row should be repainted or not
    // @OVERRIDE
    shouldUpdateCell : function() { return true; }
});</pre><p></p>
<p>The snippet above looks harmless but it could very well lead to an issue if the <code>shouldUpdateCell</code> method is renamed or removed from the Ext GridView class. Overrides like this should be considered as the last resort to solve a particular problem. If there is a way to solve the issue by relying on the public Ext JS API instead it is definitely preferred. In real life applications, it&#8217;s hard to completely avoid doing such overrides though, so how do you best deal with these overrides? We already tried ignoring the overrides, and it turns out that ignoring a problem doesn&#8217;t really solve it. <img src='http://www.bryntum.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  </p>
<h4>Dealing with overrides</h4>
<p>In our upcoming 2.2 version, each override of a private method is annotated with a <code>// @OVERRIDE</code> comment to warn anyone reading the code. If unit tests start breaking when we try a new version of Ext JS, those methods are prime candidates to review. This however is not enough, we can do better. I just wrote a simple Siesta unit test that will help us identify our weak spots as we upgrade to newer versions of Ext JS. It turned out to be very easy:</p>
<p></p><pre class="crayon-plain-tag">StartTest(function (t) {
    t.expectGlobal('Docs'); // JsDuck

    // Ignore some symbols that should be ignore + some bugs in the Ext docs
    var ignoreRe = /Ext.dd|DragZone.destroy|DragDrop.destroy|DragSource.destroy|Ext.grid.plugin.Editing.init|afterRender|initComponent|Ext.Base.configClass|Ext.Base.destroy/;

    var isPrivate = function(fullName) {
        var priv = false;

        Ext.Array.each(Docs.data.search, function(property) {

            if (property.fullName === fullName){
                priv = property.meta.private;
                return false;
            }
        });

        return priv;
    };


    function findInSuperClasses(sourceCls, property) {
        var cls = sourceCls.superclass.self;

        while (cls && cls.prototype) {
            var name = Ext.ClassManager.getName(cls);
            var fullName = name + '.' + property;

            if (name.match(/^Ext./) &&
                !ignoreRe.test(fullName) &&
                cls.prototype.hasOwnProperty(property))
            {
                if (isPrivate(fullName)) {
                    return name;
                } else {
                    return false;
                }
            }
            cls = cls.superclass && cls.superclass.self;
        }

        return false;
    }

    var MAX_NBR_OVERRIDES = 10;
    var nbrFound = 0;

    Ext.iterate(Ext.ClassManager.classes, function (className, constr) {
        if (!className.match('Sch.')) return;

        for (var o in constr.prototype) {

            // Check only own properties, and only functions for now
            if (constr.prototype.hasOwnProperty(o) && typeof constr.prototype[o] === 'function') {
                var result = findInSuperClasses(constr, o);

                if (result) {
                    t.todo(function(t) {
                        t.fail('Class ' + className + ' overrides ' + result + ':' + o);
                    })
                    nbrFound++;
                }
            }
        }
    });

    t.isLessOrEqual(nbrFound, MAX_NBR_OVERRIDES, 'Should not introduce new overrides of private methods');
});</pre><p></p>
<p>The test isn&#8217;t 100% fool proof, you could still override classes at run time that won&#8217;t be detected by this test (though that is a very unusual practice). Running this test against the latest official Ext Scheduler release reports 65 overrides, which is waaaay too many. This is very clear proof and it explains why we&#8217;ve been experiencing painful upgrades. After our 2.2 refactorings, the result looks a lot better:</p>
<p></p><pre class="crayon-plain-tag">Launching PhantomJS 1.6.0 at http://lh/ExtScheduler/tests/index-no-ui.html

fail 1 - Class Sch.mixin.Lockable overrides Ext.grid.Lockable:constructLockablePlugins
fail 2 - Class Sch.mixin.Lockable overrides Ext.grid.Lockable:injectLockable
fail 3 - Class Sch.model.Customizable overrides Ext.data.Model:afterEdit
fail 4 - Class Sch.data.FilterableNodeStore overrides Ext.data.NodeStore:onNodeExpand
fail 5 - Class Sch.selection.EventModel overrides Ext.selection.Model:bindComponent
fail 6 - Class Sch.selection.EventModel overrides Ext.selection.Model:onSelectChange
fail 7 - Class Sch.plugin.TreeCellEditing overrides Ext.grid.plugin.CellEditing:onEditComplete
fail 8 - Class Sch.view.TimelineGridView overrides Ext.view.Table:shouldUpdateCell
fail 9 - Class Sch.view.TimelineGridView overrides Ext.view.View:processUIEvent

[FAIL]  sanity/017_override_scan.t.js?Ext=external
There are failures</pre><p></p>
<p>We have now brought it down to 9 overrides, which is a lot more manageable. For an extremely small investment (the test took me < 1hr to write) we now have a very good overview of our component weak spots when it&#8217;s time for the next upgrade. As the 2.2 version relies a lot more on the public API, upgrading to and supporting new Ext JS versions should hopefully be much easier.</p>
<p>How do you deal with overrides in your application? Please share any tips you have in the comments section?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/road-to-ext-scheduler-v2-2-overrides/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Managing stores in a complex component</title>
		<link>http://www.bryntum.com/blog/managing-stores-in-a-complex-component/</link>
		<comments>http://www.bryntum.com/blog/managing-stores-in-a-complex-component/#comments</comments>
		<pubDate>Wed, 30 Jan 2013 17:31:10 +0000</pubDate>
		<dc:creator>Mats Bryntse</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Component]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[Store]]></category>

		<guid isPermaLink="false">http://www.bryntum.com/?p=2480</guid>
		<description><![CDATA[<p>The Ext Gantt chart deals with a lot of observable data stores: Tasks, Dependencies, Assignments, Resources etc. A typical view component will observe its data stores and react to their changes. When building a view component that consumes data from one or more stores, it&#8217;s important to remember to de-register any store listeners in the Component destructor. If you don&#8217;t, you can end up with hard-to-trace errors after creating multiple ...]]></description>
				<content:encoded><![CDATA[<p>The Ext Gantt chart deals with a lot of observable data stores: Tasks, Dependencies, Assignments, Resources etc. A typical view component will observe its data stores and react to their changes. When building a view component that consumes data from one or more stores, it&#8217;s important to remember to de-register any store listeners in the Component destructor. If you don&#8217;t, you can end up with hard-to-trace errors after creating multiple views using the same store. Another common case where this happens is if you have a view component in a window that can be destroyed/closed. Once it&#8217;s re-opened, a new component instance is created and is tied to the same original data store. Consider this simple piece of code.</p>
<p></p><pre class="crayon-plain-tag">var taskStore = new Gnt.data.TaskStore();

var gantt = new Gnt.panel.Gantt({
    taskStore : taskStore
});

var gantt2 = new Gnt.panel.Gantt({
    taskStore : taskStore
});

gantt.destroy();

taskStore.getById(123).set('Name', 'Foo');</pre><p></p>
<p>If the task store listeners aren&#8217;t cleaned up properly in the Gantt chart destructor, then code may be triggered in the first Gantt panel instance by a store listener even though the panel itself is destroyed. These errors are often tricky to debug and a good idea is to test this, it&#8217;s even quite easy. This is what we use for our tests to make sure we don&#8217;t &#8216;leak&#8217; any listeners.</p>
<p></p><pre class="crayon-plain-tag">StartTest(function(t) {
    t.diag('Gantt not rendered');

    var assignmentStore = t.getAssignmentStore();
    var resourceStore = t.getResourceStore();
    var dependencyStore = t.getDependencyStore();

    var taskStore = t.getTaskStore({
        dependencyStore : dependencyStore,
        resourceStore   : resourceStore,
        assignmentStore : assignmentStore
    });

    t.snapShotListeners(taskStore, 'taskStore');
    t.snapShotListeners(taskStore.nodeStore, 'nodeStore');
    t.snapShotListeners(dependencyStore, 'dependencyStore');
    t.snapShotListeners(resourceStore, 'resourceStore');
    t.snapShotListeners(assignmentStore, 'assignmentStore');

    var g = t.getGantt({
        taskStore       : taskStore,
        assignmentStore : assignmentStore,
        resourceStore   : resourceStore,
        dependencyStore : dependencyStore
    });

    // Should clean all listeners
    g.destroy();

    t.verifyListeners(taskStore,  'taskStore', 'Listeners cleaned up on taskStore');
    t.verifyListeners(assignmentStore,  'assignmentStore', 'Listeners cleaned up on assignmentStore');
    t.verifyListeners(dependencyStore,  'dependencyStore', 'Listeners cleaned up on dependencyStore');
    t.verifyListeners(resourceStore,  'resourceStore', 'Listeners cleaned up on resourceStore');
    t.verifyListeners(taskStore.nodeStore,  'nodeStore', 'Listeners cleaned up on nodeStore');

    t.diag('Gantt rendered then destroyed');

    g = t.getGantt({
        renderTo        : Ext.getBody(),
        columnLines     : true,
        taskStore       : taskStore,
        assignmentStore : assignmentStore,
        resourceStore   : resourceStore,
        dependencyStore : dependencyStore
    });

    g.destroy();

    t.verifyListeners(taskStore,  'taskStore', 'Rendered: Listeners cleaned up on taskStore');
    t.verifyListeners(assignmentStore,  'assignmentStore', 'Rendered: Listeners cleaned up on assignmentStore');
    t.verifyListeners(dependencyStore,  'dependencyStore', 'Rendered: Listeners cleaned up on dependencyStore');
    t.verifyListeners(resourceStore,  'resourceStore', 'Rendered: Listeners cleaned up on resourceStore');
    t.verifyListeners(taskStore.nodeStore,  'nodeStore', 'Rendered: Listeners cleaned up on nodeStore');
});</pre><p></p>
<p>Above you can see that we verify that no listeners are left for 5 different stores, both when not rendering the panel and when rendering the panel (as we shouldn&#8217;t assume when the listeners are added). In our TestClass for Siesta we have added these two useful methods for Observables:</p>
<p></p><pre class="crayon-plain-tag">Class('Bryntum.Test', {

    isa: Siesta.Test.ExtJS,
    
    methods: {

        snapShotListeners : function(observable, name) {
            this._observableData = this._observableData || {};

            if (!name) throw 'Must provide a name for the observable';

            this._observableData[name] = this.global.Ext.apply({}, observable.hasListeners);

            // Delete new Ext JS 4.2 properties
            if ('_decr_' in this._observableData[name]) {
                delete this._observableData[name]._decr_;
                delete this._observableData[name]._incr_;
            }
        },

        verifyListeners : function(observable, name, description) {
            var needListeners = this._observableData[name];

            this.isDeeply(observable.hasListeners, needListeners, description);
        }
    }
    // eof methods
})</pre><p></p>
<p>Hope this helps you test your Observables!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bryntum.com/blog/managing-stores-in-a-complex-component/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
