Results 1 to 5 of 5

Thread: Container.getEl() returns undefined causing the script to halt execution

  1. #1
    Sencha User
    Join Date
    Sep 2010
    Location
    Bremen, Germany
    Posts
    41

    Default Container.getEl() returns undefined causing the script to halt execution

    Hi,
    I am trying to implement a custom component and have some issues with getEl() returning an undefined reference. I am accessing the dom element from within afterRender() so I thought by that time the container has been added to the dom thus containing a valid reference to the dom element. But the following code causes the following error on chrome:
    Uncaught TypeError: Cannot call method 'getWidth' of undefined
    Can someone tell me what to do so this doesn't happen again? When can I be sure that the component has been added to the dom and that the references to the dom elements are valid?

    Here is the code in a selfcontained index.html file (make sure there is a test.png file in the same directory where the html file is placed):
    Code:
    <html>
    <head>
    <title></title>
    <style type="text/css">@import "libs/sencha-touch/resources/css/ext-touch.css";</style>
    <style type="text/css">
        .screen {
            background-color: rgb(220,220,220);
            background-image: none;
        }
    </style>
    <script type="text/javascript" src="libs/sencha-touch/ext-touch-debug.js"></script>
    <!-- <script type="text/javascript" src="js/sencha-touch.js"></script> -->
    
    <script type="text/javascript">
    String.prototype.isEmpty = function() {
        return (this == '');
    }
    
    function getRandomColor() {
        var min = 220; // don't set min to a value greate than 255
        var ar = new Array();
        for (var i = 0; i < 3; ++i) {
            ar.push(min + parseInt(Math.random() * (255 - min)));
        }
        var color = 'rgb(' + ar.join(",") + ')';
        return color;
    }
    
    Ext.setup({
        fullscreen: true,
        statusBarStyle: 'black-translucent',
        icon: 'icon.png',
        tabletStartupScreen: 'tablet_startup.png',
        phoneStartupScreen: 'phone_startup.png',
        glossOnIcon: true,
        onReady: function() {
            
            
            VideoPlaybackWidget = Ext.extend(Ext.Panel, {
        initComponent: function() {
    
            var beforeVideoCnt = new VideoPreviewWidget({
                height: 320,
                style: {
                    border: '1px solid red'
                }
            });
            beforeVideoCnt.setSource('test.png');
    
            // Initializing component
            Ext.apply(this, {
                layout: {
                    type: 'fit'
                },
                defaults: {
                    layout: {
                        align: 'stretch'
                    }
                },
                items: [{
                    xtype: 'container',
                    items: [beforeVideoCnt]
    
                }]
            });
    
            VideoPlaybackWidget.superclass.initComponent.apply(this, arguments);
        }
    });
    
    Ext.reg('videoplayblackwidget', VideoPlaybackWidget);
    
    VideoPreviewWidget = Ext.extend(Ext.Container, {
        initComponent: function() {
    
            this.source = '';
    
            /**
             * @param: s is the new source of the image
             */
            this.setSource = function(s) {
                if (s == undefined) {
                    // assign nothing to source
                }
                else if (typeof s != 'string') {
                    console.error('TypeError');
                    return;
                }
                else {
                    this.source = s;
                }
    
                function waitUntilLoaded(scope) {
                    if (!img.complete) {
                        console.log('scheduled for later');
                        setTimeout(waitUntilLoaded, 100, scope);
    
                    } else {
    
                        var w = img.width;
                        var h = img.height;
    
                        var aspectRatio = scope.getWidth() / scope.getHeight();
                        if (w == 0 || h == 0) {
                            w = scope.getWidth();
                            h = scope.getHeight();
                        } else {
                            var aspectRatio = w / h;
                        }
    
                        if (w > scope.getWidth()) {
                            w = scope.getWidth();
                            h = w / aspectRatio;
                        }
    
                        if (h > scope.getHeight()) {
                            h = scope.getHeight();
                            w = h * aspectRatio;
                        }
    
                        var el = scope.getEl();
                        if (el) {
                            var imgEl = el.down('img');
                            imgEl.set({
                                style: (scope.source == null || scope.source.isEmpty()) ? 'display:none;' : 'display: block;',
                                src: scope.source,
                                alt: 'image path:' + scope.source,
                                width: (w == undefined) ? 'auto' : w,
                                height: (h == undefined) ? 'auto' : h
                            });
                            repositionElements(imgEl, w, h, scope);
                        }
                    }
                };
    
                var img = new Image();
                img.src = this.source;
                waitUntilLoaded(this);
            };
    
            this.getSource = function() {
                return this.source;
            };
    
            function repositionElements(el, w, h, scope) {
    
                var xoffset = Math.floor((scope.width - w) / 2);
                var yoffset = Math.floor((scope.height - h) / 2);
    
                var el = scope.getEl();
                if (el) {
                    var imgEl = el.down('img');
                    console.log('left:' + xoffset + 'px; top:' + yoffset + 'px;');
    
                    imgEl.set({
                        style: 'left:' + xoffset + 'px; top:' + yoffset + 'px;'
                    });
                }
            };
    
            this.test = function(obj) { // DEBUG
                console.log('on show');
            };
    
    
            /* Initializing component */
            Ext.apply(this, {
                html: '<div style="position:relative;"><img style="position: absolute;"/><div style="position: absolute;"></div></div>'
            });
    
            VideoPreviewWidget.superclass.initComponent.apply(this, arguments);
        },
    
        afterRender: function() {
            VideoPreviewWidget.superclass.afterRender.apply(this, arguments);
            setTimeout(this.setSource(), 10000);
        }
    });
    
    Ext.reg('videopreviewwidget', VideoPreviewWidget);
                    
            var screen = new VideoPlaybackWidget({
                fullscreen: true
            });
            screen.show();
            
        }
    });
    
    
    </script>
    </head>
    
    <body>
    </body>
    </html>

  2. #2
    Sencha User Animal's Avatar
    Join Date
    Mar 2007
    Location
    Bédoin/Nottingham
    Posts
    30,892

    Default

    Apart from anything, you are overnesting.

    Your main, "fullscreen" Panel contains a Container which contains a VideoPreviewWidget.

    It should just contain a VideoPreviewWidget

    Code:
            // Initializing component
            Ext.apply(this, {
                layout: {
                    type: 'fit'
                },
                items: [beforeVideoCnt]
            });
    As to your problem. There will not be an Element to get until the Panel has been rendered.

    Your VideoPreviewWidget is bizarre. You create a new Image(), and on load, you set the original imgEl's src to the source? Why not just set the imgEl's src right away (Well, onRender anyway!)

  3. #3
    Sencha User
    Join Date
    Sep 2010
    Location
    Bremen, Germany
    Posts
    41

    Default

    Quote Originally Posted by Animal View Post
    Apart from anything, you are overnesting.

    Your main, "fullscreen" Panel contains a Container which contains a VideoPreviewWidget.

    It should just contain a VideoPreviewWidget

    Code:
            // Initializing component
            Ext.apply(this, {
                layout: {
                    type: 'fit'
                },
                items: [beforeVideoCnt]
            });
    As to your problem. There will not be an Element to get until the Panel has been rendered.

    Your VideoPreviewWidget is bizarre. You create a new Image(), and on load, you set the original imgEl's src to the source? Why not just set the imgEl's src right away (Well, onRender anyway!)
    Hi,
    thank you for replying. Well, that example I sent you is actually a simplified version. The original version is much more nested than this one but of course there is more than one component inside the layout. I jus took away the other elements for this example to make the code more readable.

    The new Image is just for reading out the width/height of the Image in order to be able to center align the image on the container. I want to be able to set the image right away when instantiating the object, i.e. before it has been added to any other container and displayed. By that time the actual size of the Container (for example it's width when align: stretch has been passed) is unknown since it hasn't been yet rendered. Container.getEl() will also return undefined so I can't set any attributes anyway. So I want the setSource function to just set the string variable this.source to the specified source and drop out of the function.

    By the time the widget is rendered however - and I thought that would be when afterRender is executed - I expect the dom elements to be in place and want to do the processing so this time setSource is called again without a parameter in which case it defaults back to this.source. So it should be able to set the attributes of the image element now since the getEl() returns a valid reference. When using onRender it seems like even getEl() is defined, the image element I defined in the html property are still null so I cant call set on them. That's why I resorted to afterRender().

    I have changed the code a little so I have a separate function updateComponent that is called when I call setSource and also from withing afterRender. The problem now is that Container.getWidth() and Container.getHeight() provide incorrect values. In Chrome's debugger I see that the container has a width of about 950px. Container.getWidth() however returns some values like 2pixels. Shouldn't getWidth() provide the actual width when called from withing afterRender()?

    Thanks in advance

    Code:
    VideoPreviewWidget = Ext.extend(Ext.Container, {
        initComponent: function() {
    
            this.source = '';
    
             this.updateComponent = function(scope) {
                if (scope.getEl() == undefined || scope.getEl() == null)
                    return;
    
                 var img = new Image();
                img.src = scope.source;
                waitUntilLoaded(scope);
    
                function waitUntilLoaded(scope) {
                    if (!img.complete) {
                        console.log('image not loaded yet');
                        setTimeout(waitUntilLoaded, 100, scope);
    
                    } else {
                        var w = img.width;
                        var h = img.height;
                        cntW = scope.getWidth(); // container width
                        cntH = scope.getHeight(); // container height
    
                        var aspectRatio = cntW / cntH;
                        if (w == 0 || h == 0) {
                            w = cntW;
                            h = cntH;
                        } else {
                            var aspectRatio = w / h;
                        }
    
                        if (w > cntW) {
                            w = cntW;
                            h = w / aspectRatio;
                        }
    
                        if (h > cntH) {
                            h = cntH;
                            w = h * aspectRatio;
                        }
    
                        var el = scope.getEl();
                        if (el) {
                            var imgEl = el.down('img');
    
                            imgEl.set({
                                style: (scope.source == null || scope.source.isEmpty()) ? 'display:none;' : 'display: block;',
                                src: scope.source,
                                alt: 'image path:' + scope.source,
                                width: (w == undefined) ? 'auto' : w,
                                height: (h == undefined) ? 'auto' : h
                            });
                            repositionElements(imgEl, w, h, scope);
                        }
                    }
                };
             }
    
            this.setSource = function(s) {
                if (s == undefined || typeof s != 'string')
                    return;
    
                    this.source = s;
                    this.updateComponent(this);
            };
    
            this.getSource = function() {
                return this.source;
            };
    
            function repositionElements(el, w, h, scope) {
    
                var xoffset = Math.floor((scope.getWidth() - w) / 2);
                var yoffset = Math.floor((scope.getHeight() - h) / 2);
    
                console.log(scope.getWidth() + ', '+ scope.getHeight());
                var el = scope.getEl();
                if (el) {
                    var imgEl = el.down('img');
                    console.log('left:' + xoffset + 'px; top:' + yoffset + 'px;');
    
                    imgEl.set({
                        style: 'left:' + xoffset + 'px; top:' + yoffset + 'px;'
                    });
                }
            };
    
            /* Initializing component */
            Ext.apply(this, {
                html: '<div style="position:relative;"><img style="position: absolute;"/><div style="position: absolute;"></div></div>'
            });
    
            VideoPreviewWidget.superclass.initComponent.apply(this, arguments);
        },
    
        afterRender: function() {
            VideoPreviewWidget.superclass.afterRender.apply(this, arguments);
            this.updateComponent(this);
        }
    });
    
    Ext.reg('videopreviewwidget', VideoPreviewWidget);

  4. #4
    Sencha Premium User evant's Avatar
    Join Date
    Apr 2007
    Location
    Sydney, Australia
    Posts
    19,256

    Default

    It's kind of an odd way to write an extended class. What are you trying to achieve?
    Twitter - @evantrimboli
    Former Sencha framework engineer, available for consulting.
    As of 2017-09-22 I am not employed by Sencha, all subsequent posts are my own and do not represent Sencha in any way.

  5. #5
    Sencha User
    Join Date
    Sep 2010
    Location
    Bremen, Germany
    Posts
    41

    Default

    Quote Originally Posted by evant View Post
    It's kind of an odd way to write an extended class. What are you trying to achieve?
    Well, actually I am just trying to implement a Container that displays a center-aligned image that is resized to the fit into the container (if the image size exceeds the bounds of the container) while keeping it's aspect ratio. The problem is probably that I am new to javascript and Sencha in general and don't know how to subclass properly and override the default behaviour etc. So far I've found only one document describing the process of subclassing in Sencha using Ext.extend() (http://www.sencha.com/learn/Tutorial...ew_UI_controls) but the proper process of doing this is still more or less unclear to me. I have a background in C++/Qt and there I would simply introduce a Constructor, pass some arguments to the superclass constructor and add methods to the class and/or override event handlers. In Sencha we have initComponent(), constructor() and all those template methods that need to be overridden. It's all a little confusing. I've got my code to work somehow (see the pasted code below) but it's definitely far from perfect. It would be nice to get some feedback/advice about what I am doing wrong and what the proper way of doing this would like.

    Thanks in advance

    Code:
            VideoPreviewWidget = Ext.extend(Ext.Container, {
                initComponent: function() {
                    this.source = '';
                    this.dirty = false;
    
                    this.alignElements = function(img) {
                        if (this.getEl() == undefined || this.getEl() == null) {
                            console.log('warning: alignElements(): el undefined!');
                            return;
                        }
    
                        var imgEl = this.getEl().down('img');
    
                        var w = img.width;
                        var h = img.height;
    
                        var cntW = this.getEl().getWidth(true); // container width
                        var cntH = this.getEl().getHeight(true); // container height
                        console.info('image size:' + w + 'x' + h);
                        console.info('container size:' + cntW + 'x' + cntH);
    
                        var aspectRatio = cntW / cntH;
                        if (w == 0 || h == 0) {
                            w = cntW;
                            h = cntH;
                        } else {
                            var aspectRatio = w / h;
                        }
    
                        if (w > cntW) {
                            w = cntW;
                            h = w / aspectRatio;
                        }
    
                        if (h > cntH) {
                            h = cntH;
                            w = h * aspectRatio;
                        }
    
                        w = Math.round(w);
                        h = Math.round(h);
    
                        var styleWidth = 'width:' + ((w == undefined || w == NaN) ? 'auto; ' : w + 'px; ');
                        var styleHeight = 'height:' + ((h == undefined || h == NaN) ? 'auto; ' : h + 'px; ');
    
                        console.info('adjusted image size:' + w + 'x' + h); // DEBUG
                        var xoffset = Math.round((cntW - w) / 2);
                        var yoffset = Math.round((cntH - h) / 2);
    
                        var styleLeft = 'left:' + ((xoffset == undefined || xoffset == NaN) ? 'auto; ' : xoffset + 'px; ');
                        var styleTop = 'top:' + ((yoffset == undefined || yoffset == NaN) ? 'auto; ' : yoffset + 'px; ');
    
                        var dsp = (this.source == null || this.source.isEmpty()) ? 'display:none; ' : 'display:block;';
                        var style = styleWidth + styleHeight + styleLeft + styleTop + dsp;
    
                        console.info(style); // DEBUG
                        imgEl.set({
                            style: style,
                            src: this.source,
                            alt: 'image path:' + this.source
                        });
    
                        imgEl.set({
                            style: 'left:' + xoffset + 'px; top:' + yoffset + 'px;'
                        });
                    }
    
                    this.setSource = function(s) {
                        if (s == undefined || typeof s != 'string') return;
    
                        this.source = s;
    
                        var el = this.getEl();
                        if (this.getEl() == undefined || this.getEl() == null) this.dirty = true;
                        else this.setImage();
                    };
    
                    this.setImage = function() {
                        var preloadImg = new Image();
                        
                        el = this;
                        preloadImg.onload = function() { el.alignElements(preloadImg); };
                        preloadImg.src = this.source;
    
                        this.dirty = false;
                    }
    
                    this.getSource = function() {
                        return this.source;
                    };
    
                    function repositionElements(el, w, h, scope) {
    
                    };
    
                    /* Initializing component */
                    Ext.apply(this, {
                        html: '<div style="position:relative;"><img style="position: absolute;"/><div style="position: absolute;"></div></div>'
                    });
    
                    VideoPreviewWidget.superclass.initComponent.apply(this, arguments);
                },
    
                afterRender: function() {
                    VideoPreviewWidget.superclass.afterRender.apply(this, arguments);
                    var el = this.getEl().down('img');
                    if (this.dirty) this.setImage();
                }
            });
    
    
            Ext.reg('videopreviewwidget', VideoPreviewWidget);

Similar Threads

  1. MessageBox to halt browser execution
    By gauravk in forum Ext 2.x: Help & Discussion
    Replies: 2
    Last Post: 22 Jan 2009, 12:55 AM
  2. Stop script execution until fx ends
    By phosky in forum Ext 2.x: Help & Discussion
    Replies: 7
    Last Post: 13 Mar 2008, 8:28 AM
  3. Trying to hide labels...What to do when field.getEl() returns undefined
    By TheNakedPirate in forum Ext 2.x: Help & Discussion
    Replies: 2
    Last Post: 16 Jan 2008, 9:29 PM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •