How to embed an Ext JS 5+ app in a div on an external app/website

Posted on in Ext JS 5 & 6 Questions

Sometimes, you would need to embed Sencha applications into an existing portal or CMS.
(Please note, often this requires an OEM license).

Since Ext JS 5 is mainly focussed on creating single page apps, this can be a tricky thing to achieve.
There are a couple of solutions you could look into.

  • 1) Using Ext.onReady() in combination with loading ExtJS-all.js. This is the way how Sencha prototypes examples in the API docs.
    It works. - But it's hard to re-use, hard to theme, and no use of Sencha Cmd.
  • 2) Using an iframe.
    For sure, the most easy solution but it's an iframe.
  • 3) Using the multiple apps approach.
    This should be the preferred Sencha way. But note, the Ext JS viewport takes over the entire screen.

The last solution could be the ideal solution. See the Sencha guides to read more about this:

http://docs.sencha.com/cmd/5.x/workspaces.html
http://docs.sencha.com/cmd/5.x/advanced_cmd/cmd_multi.html

Since the multiple apps approach uses the Sencha viewport, the viewport will take over the entire screen, which can be something what you don't want.
Unfortunately there is no out of the box solution to embed Sencha applications into div elements.

There are 2 things you can do.

1) Override the Ext.plugin.Viewport to let it embed in HTML elements.
2) Insert the HTML parts into a Sencha Component to let it be part of a Sencha layout.

Rendering the viewport in an HTML div element.

This approach would require you to override the viewport plugin.

/**
 * This plugin can be applied to any `Component` (although almost always to a `Container`)
 * to make it fill the browser viewport. This plugin is used internally by the more familiar
 * `Ext.container.Viewport` class.
 *
 * The `Viewport` container is commonly used but it can be an issue if you need to fill the
 * viewport with a container that derives from another class (e.g., `Ext.tab.Panel`). Prior
 * to this plugin, you would have to do this:
 *
 *      Ext.create('Ext.container.Viewport', {
 *          layout: 'fit', // full the viewport with the tab panel
 *
 *          items: [{
 *              xtype: 'tabpanel',
 *              items: [{
 *                  ...
 *              }]
 *          }]
 *      });
 *
 * With this plugin you can create the `tabpanel` as the viewport:
 *
 *      Ext.create('Ext.tab.Panel', {
 *          plugins: 'viewport',
 *
 *          items: [{
 *              ...
 *          }]
 *      });
 *
 * More importantly perhaps is that as a plugin, the view class can be reused in other
 * contexts such as the content of a `{@link Ext.window.Window window}`.
 *
 * The Viewport renders itself to the document body, and automatically sizes itself to the size of
 * the browser viewport and manages window resizing. There may only be one Viewport created
 * in a page.
 *
 * ## Responsive Design
 *
 * This plugin enables {@link Ext.mixin.Responsive#responsiveConfig} for the target component.
 *
 * @since 5.0.0
 */
Ext.define('Demo.override.plugin.Viewport', {
    override: 'Ext.plugin.Viewport',

    alias: 'plugin.viewport',

    /**
     * @cfg {Number} [maxUserScale=1]
     * The maximum zoom scale. Only applicable for touch devices. Set this to 1 to
     * disable zooming.  Setting this to any value other than "1" will disable all
     * multi-touch gestures.
     */

    setCmp: function (cmp) {
        this.cmp = cmp;

        if (cmp && !cmp.isViewport) {
            this.decorate(cmp);
            if (cmp.renderConfigs) {
                cmp.flushRenderConfigs();
            }
            cmp.setupViewport();
        }
    },

    statics: {
        decorate: function (target) {
            Ext.applyIf(target.prototype || target, {
                ariaRole: 'application',

                viewportCls: Ext.baseCSSPrefix + 'viewport'
            });

            Ext.override(target, {
                isViewport: true,

                preserveElOnDestroy: true,

                initComponent : function() {
                    this.callParent();
                    this.setupViewport();
                },

                handleViewportResize: function () {
                    var me = this,
                        Element = Ext.dom.Element,
                        width = Element.getViewportWidth(),
                        height = Element.getViewportHeight();

                    if (width != me.width || height != me.height) {
                        me.setSize(width, height);
                    }
                },

                setupViewport : function() {

                    var me = this,
                        targetEl = me.renderTo,
                        el;

                    if(targetEl){
                        el = me.el = Ext.get(targetEl);
                    }
                    else {
                        el = me.el = Ext.getBody();
                    }

                    // Get the DOM disruption over with before the Viewport renders and begins a layout
                    Ext.getScrollbarSize();

                    // Clear any dimensions, we will size later on
                    me.width = me.height = undefined;

                    // Andrea: Need to comment this
                    //Ext.fly(el).addCls(me.viewportCls);
                    el.setHeight = el.setWidth = Ext.emptyFn;
                    el.dom.scroll = 'no';
                    me.allowDomMove = false;
                    me.renderTo = el;

                    if (Ext.supports.Touch) {
                        me.initMeta();
                    }
                },

                afterLayout: function(layout) {
                    if (Ext.supports.Touch) {
                        this.el.scrollTop = 0;
                    }
                    this.callParent([layout]);
                },

                onRender: function() {
                    var me = this,
                        el = me.el;

                    me.callParent(arguments);

                    // Important to start life as the proper size (to avoid extra layouts)
                    // But after render so that the size is not stamped into the body
                    me.width = el.getWidth();
                    me.height = el.getHeight();

                    // prevent touchmove from panning the viewport in mobile safari
                    if (Ext.supports.TouchEvents) {
                        me.mon(el, {
                            touchmove: function(e) {
                                e.preventDefault();
                            },
                            translate: false,
                            delegated: false
                        });
                    }
                },

                initInheritedState: function (inheritedState, inheritedStateInner) {
                    var me = this,
                        root = Ext.rootInheritedState;

                    if (inheritedState !== root) {
                        // We need to go at this again but with the rootInheritedState object. Let
                        // any derived class poke on the proper object!
                        me.initInheritedState(me.inheritedState = root,
                            me.inheritedStateInner = Ext.Object.chain(root));
                    } else {
                        me.callParent([inheritedState, inheritedStateInner]);
                    }
                },

                beforeDestroy: function(){
                    var me = this,
                        root = Ext.rootInheritedState,
                        key;

                    // Clear any properties from the inheritedState so we don't pollute the
                    // global namespace. If we have a rtl flag set, leave it alone because it's
                    // likely we didn't write it
                    for (key in root) {
                        if (key !== 'rtl') {
                            delete root[key];
                        }
                    }

                    me.removeUIFromElement();
                    me.el.removeCls(me.baseCls);
                    Ext.fly(document.body.parentNode).removeCls(me.viewportCls);
                    me.callParent();
                },

                addMeta: function(name, content) {
                    var meta = document.createElement('meta');

                    meta.setAttribute('name', name);
                    meta.setAttribute('content', content);
                    Ext.getHead().appendChild(meta);
                },

                initMeta: function() {
                    var me = this,
                        maxScale = me.maxUserScale || 1;

                    me.addMeta('viewport', 'width=device-width, initial-scale=1, maximum-scale=' +
                           maxScale + ', user-scalable=' + (maxScale !== 1 ? 'yes' : 'no'));
                    me.addMeta('apple-mobile-web-app-capable', 'yes');
                },

                privates: {
                    // override here to prevent an extraneous warning
                    applyTargetCls: function (targetCls) {
                        this.el.addCls(targetCls);
                    },
                    
                    // Override here to prevent tabIndex set/reset on the body
                    disableTabbing: function() {
                        var el = this.el;
                        
                        if (el) {
                            el.saveChildrenTabbableState();
                        }
                    },
                    
                    enableTabbing: function() {
                        var el = this.el;
                        
                        if (el) {
                            el.restoreChildrenTabbableState();
                        }
                    },

                    getOverflowEl: function() {
                        return Ext.get(document.documentElement);
                    }
                }
            });
        }
    },

    privates: {
        updateResponsiveState: function () {
            /* Andrea: Need to comment this for now, otherwise stretching In and Out
             * the browser will make the viewport full screen again. */
            //this.cmp.handleViewportResize();
            //this.callParent();
        }
    }
},
function (Viewport) {
    Viewport.prototype.decorate = Viewport.decorate;
});

The app.js should disable the autoCreateViewport, and in your app launch method, you should create the main interface like this:

Ext.create('MyApp.view.main.Main',{
   requires: ['Ext.plugin.Viewport'],
   renderTo: 'mydiv',
   plugins: [{
    ptype: 'viewport'
   }]
});

Please note, this is just a quick code example, I'm not sure if it's 100% functional.

Rendering contents in the viewport

As an alternative to the viewport take over, and rending the contents in a div. You could also render the HTML parts in the viewport. (so the otherway arround).

Such an example can be found in the Ext JS 5 example apps:

http://dev.sencha.com/ext/5.0.1/examples/calendar/index.html

This might be a nice example for you to inspect as well. The index.html includes the full ext-all framework. (they also have some magic, in using various themes, please see shared/include-ext.js)
Index.html has also also it's own header. The trick here, will be to use contentEl, to extract that HTML part and insert it into an Ext JS Component, so it can be part of the Sencha (border) layout.
(contentEl, should point to an html id).
Open src/App.js, and notice how they create a new viewport in the constructor. Note: By using the contentEl, the header will be overridden with Sencha styles. You will need to tweak the styling probably.

I hope these ideas can help you further!

Make a native build with Ext JS 5, Sencha Cmd 5 and Phonegap / Cordova with plugins.

Posted on in Cmd Cordova Ext JS Ext JS 5 Mobile Node JS Sencha

With the release of Ext JS 5; we finally have Touch experience in the framework. So yes, you can start creating tablet applications.
Maybe you are now wondering, can I also package Ext JS 5 apps with Apache Cordova or Adobe Phonegap, so I can sell my app in the App Stores?
Well yes, you can!

Here are the steps I took; for porting my app with Cordova/Phonegap.
**note, instead of using the keyword phonegap, Cordova users may use the keyword cordova instead.
**note2: you will need to have the following dependencies installed: (Sencha Cmd, Node JS and Phonegap or Cordova)

There we go:

  1. Let's generate a new Ext JS 5 app
    Browse with your terminal to your Ext JS 5 SDK folder. Run the following command: sencha generate app MyApp ../phonegapdemo Here we generate an Ext JS 5 demo app, in the phonegapdemo folder, which stands next to the downloaded extjs5 sdk folder.
  2. Open app.json
    Add the following code block:
        "builds": {
            "ios": { //build name
                "packager": "phonegap",
                "phonegap": {
                    "config": {
                        // Uncomment the line below and add the platform you wish to build for
                        "platform": "ios",
    
                        "remote": false,
                        "id": "com.domain.MyApp",
                        "name": "MyApp"
                    }
                }
            }
        },
    
    Here I'm adding a custom phonegap build for iOS. I used as a build name the keyword ios, a name i choose to recognize iOS builds, but incase you want to make for example Android builds, I could change it for android. In case of an Android I would also need to change the platform keyword. Note also the id property, which expects an id in reversed domain style; and name which should be the name of the Sencha App namespace. In case I want to build via the Phonegap cloud web service; I should set the property remote to true. Then, you will also need to create a local.properties file in the phonegapdemo folder; with the login details for build.phonegap.com:
    phonegap.remote.username=my@email.com
    phonegap.remote.password=mypassword
    
  3. Create a native build
    Back to your terminal, navigate to the phonegapdemo folder, and run the following command:
    sencha app build ios
    
    Note the keyword ios, that's the build name which was set in app.json! Mac OSX users, might need to prefix with the word sudo. In case you are using Phonegap cloud webservice, it will now upload your app. In case of Phonegap local or Cordova, this command will now generate the following folderstructure like below. Instead of the command sencha app build, I could also use the command sencha app prepare; it would prepare the folderstructure as well.

    phonegapdemo
    - phonegap
    - - config.xml
    - - hooks
    - - platforms
    - - plugins
    - - www


    The www folder, will be the place, where a copy of your Sencha Ext JS 5 app will be located. The plugin folder will contain Device API plugins after installing those. (see the steps below, woot!)
  4. Enable the JS Phonegap/Cordova API
    Although you could build and run your application on a device by now; it might be handy when you enable the Phonegap/Cordova device API. For example, in case you need to install plugins, such as the inappbrowser plugin. Open in an editor phonegapdemo/index.html and add the following line, before the micoloader bootstrap.js script:
     <script type="text/javascript" src="cordova.js"></script>
    
    You might wonder, why I won't add this cordova.js file to the js array in app.json. Not sure if I did it wrong, but I was running into sencha build errors because of that. Mind you, the cordova JavaScript file will be created while building the app; so it's not available in the project root.
  5. Let's build it (again)!
    sencha app build ios
    In case you are building with the PhoneGap cloud webservice, you can start scanning the QR code. Cordova or PhoneGap local users, can start opening the project file from the phonegapdemo/phonegap/platforms/<platform> folder, and build it via the developer toolkit.

Wait, let's add another step, as a BONUS!
What about installing the inAppBrowser plugin, to make sure PhoneGap/Cordova will open your external hyperlinks in browser window within your app!
(That's it's what you want iOS user! Cause iOS ignores opening new browser windows. grrrumbll!!!!)
These steps are for PhoneGap Local / Cordova users:

  1. Edit config.xml
    You can find it here: phonegapdemo/phonegap/config.xml Now add the following line, (if not already available):
    <gap:plugin name="org.apache.cordova.inappbrowser"></gap:plugin>
    
  2. Install the plugin:
    Run from the command-line the following command, from the phonegapdemo/phonegap folder:
    phonegap plugin add org.apache.cordova.inappbrowser
    
    Again, Mac OSX users, you will need to have admin rights, so prefix with sudo. This command will add the plugin into the phonegapdemo/phonegap/plugins/ folder.
  3. How to open URLs
    Edit the demo app, and create a button, which will open an external URL in a separate browser. For example: phonegapdemo/app/view/main/Main.js
    Ext.define('MyApp.view.main.Main', {
        extend: 'Ext.panel.Panel',
        requires: [
            'MyApp.view.main.MainController',
            'MyApp.view.main.MainModel'
        ],
    
        xtype: 'app-main',
        
        controller: 'main',
        viewModel: {
            type: 'main'
        },
    
        padding: 20,
    
        layout: 'vbox',
        items: [{
            xtype: 'button',
            scale: 'large',
            text: 'Open Web Page',
            margin: 20,
            handler: 'openWebPage'
        }]
    });
    
    And note here, the magic: window.open(). See below, the implementation in my viewcontroller: phonegapdemo/app/view/main/MainController.js
    Ext.define('MyApp.view.main.MainController', {
        extend: 'Ext.app.ViewController',
    
        requires: [
            'Ext.MessageBox'
        ],
    
        alias: 'controller.main',
    
        openWebPage : function(){
            var url = 'http://www.google.com';
            window.open(url, '_blank', 'location=no,EnableViewPortScale=yes'); 
        }
    });
    
    BAMMM!!! Build and Test your inAppBrowser, it should work!

Introducing Ext JS 5 Beta

Posted on in Ext JS 5 Sencha

Last week was a very important week for Sencha. We recently introduced Ext JS 5 to the public. It's now possible to try out the beta version!

As a Sencha employee I already had early access to this framework; that gave me enough time to play around with it. ..and I have to say... I love it!

Ext JS 5, is another step closer towards Sencha Touch. It shares a lot of code with Sencha Touch it allows the same code to power both desktop and touch device experiences, with a gesture system inspired by Sencha Touch.

Tablet support

Ext JS 5 supports IE8+ and the latest tablet platforms such as iOS6/7, Chrome on Android 4.1+, and Win 8 touch-screen devices (such as Surface and touch-screen laptops) running IE10+.

MVVM application architecture

Another very important feature of Ext JS 5 is support for a popular alternative to MVC: MVVM (standing for Model-View-ViewModel). One of the big attractions to MVVM is data binding. With data binding, you no longer have to write all of the “glue” code to connect the model layer to the view and update the model when the view is modified.

That's not all. There are lots of other cool features. I summarized a list with all the new topics:

  • Core, Class System: Private Methods, Class System change to merge with Sencha Touch, Data Package, Events, Utilities & Feature/Environment dection
  • Application Architecture, Additional MVVM (Model, View, ViewModel), Sencha Ext 4 MVC still exists, Two-way data binding, View Models, View Controllers, Routing
  • Data Package, Model validation Binding, Many to Many Associations, Chained Stores, Data Sessions, Heterogeneos Stores and TreeStores, Offline Proxies (from Touch, LocalStorage, SessionStorage, SQL)
  • Forms, Custom Field Types, Field layouts, Layout free containers, Textfield triggers
  • Tablet/ Touch Support, Neptune Touch theme, New Crisp (iOS 7 like) Theme, Event System, Gestures support
  • Grids, Widget Column (component cells), Widgets (Progress Bar, Slider, Sparkline), Buffered Updates, Cell updates, Rendering Optimizatons
  • Sencha Charts, Sencha Touch charts package, Legacy Charts available atleast till 5.1
  • New components / classes, Multi-select grid, Tag field, Ext.mixin.Mashup, Ext.dashboard.Dashboard
  • Other, Compatibility Layer, Sencha Cmd 5

Take a look into the Ext JS 5 API Docs and "What's new"-guides, to play around with all these new goodies! I'm sure, you'll love it too.

Handy Links