1. #1
    Sencha Premium Member
    Join Date
    Apr 2012
    Posts
    22
    Vote Rating
    0
    zaku is on a distinguished road

      0  

    Default Unanswered: Widget Rendered Grid

    Unanswered: Widget Rendered Grid


    Hello,

    I've implemented gxt 2 widget based on grid. For each row I've used custom GridCellRenderer wich simply returned widget to be displayed as row. It was very flexible and I could set up widgets in row using layouts etc. How can I accomplish similar in gxt 3? There is a render method to override but rows are created using SafeHtmlBuilder and append methods. I've tried doing something like converting container with it's widgets to html string and appending it but then no event handlers will work. There are Cell objects and I could use CompositeCell but there is still problem with layouting widgets inside row.

    Also I've found that there is missing one method from grid's view - setRowSelectorDepth and in GridBaseAppearance#findRow, findCell it's hardcoded to 15.

  2. #2
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,717
    Answers
    109
    Vote Rating
    88
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    GXT 3 doesn't provide for drawing widgets in the grid, at least not in the way that 2.x did. Drawing so many widgets is often very expensive, so while it was supported, it was not encouraged for the performance issues it could cause.

    In GWT 2.1, the Cell api was introduced as a way to draw complex widgets that can be interacted with more cheaply than normal widgets. These are typically used in widgets that display lots of data, but they have also encouraged them to be used when drawing widgets, by way of the CellWidget.

    In GXT 3.0, we have built most of our simpler components using cells so they can be drawn in the grid and interacted with, or used as plain widgets. While this has made this release more difficult than we initially anticipated, it does allow for very complex grids or trees of cells, capable of complex interaction, to be drawn very efficiently. The fact that most of these components also have a corresponding cell means that the features (and styling, via the appearance pattern) should be very similar whether using the component or the cell.

    You can build almost anything you would build in a widget. You can override AbstractCell's render method and create the structure of the widget in plain HTML. Converting existing widgets' HTML content should be fairly straightforward. To build out the handler behavior, you'll need to also override onBrowserEvent. For any events you plan to receive, make sure you pass them into the AbstractCell constructor.

    Check out https://developers.google.com/web-to...eUiCustomCells for additional documentation on building custom cells.

  3. #3
    Sencha Premium Member
    Join Date
    Apr 2012
    Posts
    22
    Vote Rating
    0
    zaku is on a distinguished road

      0  

    Default


    Ok, so can you tell me how to lay out cells inside AbstractCell using existing layouts? How should I know which cell is being clicked for example. In CompositeCell this is done via hasCells list indexing and traversing dom same way it was created:

    Code:
    @Override
      public void onBrowserEvent(Context context, Element parent, C value,
          NativeEvent event, ValueUpdater<C> valueUpdater) {
        int index = 0;
        EventTarget eventTarget = event.getEventTarget();
        if (Element.is(eventTarget)) {
          Element target = eventTarget.cast();
          Element container = getContainerElement(parent);
          Element wrapper = container.getFirstChildElement();
          while (wrapper != null) {
            if (wrapper.isOrHasChild(target)) {
              onBrowserEventImpl(context, wrapper, value, event, valueUpdater,
                  hasCells.get(index));
            }
    
    
            index++;
            wrapper = wrapper.getNextSiblingElement();
          }
        }
      }
    But suppose I want more complex layout. How I should fire onBrowserEvent corresponding to cell being clicked?

  4. #4
    Sencha User AgnerWagner's Avatar
    Join Date
    Apr 2012
    Location
    Россия
    Posts
    2
    Vote Rating
    0
    AgnerWagner is on a distinguished road

      0  

    Default Widget Rendered Grid

    Widget Rendered Grid


    its no problem to use a custom cell renderer within a grid. But how can I use a different header renderer? Reason: I need to put some images into my column headers. The only way I can think of is to hide the headers and render my images in the first row - but thats not really a clean way to do it.

  5. #5
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,717
    Answers
    109
    Vote Rating
    88
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    @zaku - the trick is to find a way to identify which 'sub-cell' should be getting the event. If you know the class name of the root element, or can tell based on the order of the children, that would be a good way to tell

    @AgnerWagner: Try the ColumnConfig.setHeader(SafeHtml) method - this accepts SafeHtml instances and will draw them as html elements. These can be created in many ways, including the XTemplates, SafeHtmlTemplates, the SafeHtmlBuilder, and SafeHtmlUtils classes.

  6. #6
    Sencha Premium Member
    Join Date
    Apr 2012
    Posts
    22
    Vote Rating
    0
    zaku is on a distinguished road

      0  

    Default


    @Colin Alworth - yes this is the trick. I was thinking of creating some uniform way of telling which sub-cell should get the event, so I could layout them any way I want. Using the class parameter might not be good idea. I was thinking more about assigning id for each. Anyway it's far too complicated than it should be, but I guess there is always some trade-off for performance
    Btw: it would be nice to have something like this in future releases

    @AgnerWagner - maybe you could also use ColumnConfig#setWidget(Widget, SafeHtml)

  7. #7
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,717
    Answers
    109
    Vote Rating
    88
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    Is there a specific layout you are after? Most of the layouts dont make sense to use in a cell in a grid. If you are just building, for example, a row of evenly spaced icons that are clickable, I wouldn't even bother with layout, but just make several images each with a different *css* class.

    Checking for the *Java* class is indeed probably useless, but the *css* class is something you can get from the element that the event occurred on, and using your internal model of how the different pieces are arranged, can decide which piece was interacted with.

  8. #8
    Sencha Premium Member
    Join Date
    Apr 2012
    Posts
    22
    Vote Rating
    0
    zaku is on a distinguished road

      0  

    Default


    I didn't mean Java class

    I was thinking about layouts generally. It would be cool to not to write any html and css but do everything from java code as was possible in GridCellRenderer previously.

    Now my idea (although I'm not sure it's doable) is to append container's (or any Widget's) html in Cell#render method, and then in onBrowserEvent somehow identify it's children. I also was thinking about implementing it in more reusable fashion (like CompositeCell), so I could put whatever Wigdet I want and events would just work. Now I said that css class would be probably wrong for it because if you had two buttons in Container they will have same css class so you can't identify them. That's why I thought of css id, to assign id for every compoment and it's children (using some kind of convention) and then identifying them in onBrowserEvent in dom would be easier.

  9. #9
    Sencha Premium Member
    Join Date
    Nov 2012
    Location
    Los Angeles
    Posts
    23
    Vote Rating
    0
    austinkottke is on a distinguished road

      0  

    Default CompositeCell example

    CompositeCell example


    Can someone provide a simple CompositeCell example?

  10. #10
    Sencha Premium Member
    Join Date
    Nov 2012
    Posts
    18
    Vote Rating
    1
    mpickell is on a distinguished road

      0  

    Default Any advice here?

    Any advice here?


    Quote Originally Posted by Colin Alworth View Post
    GXT 3 doesn't provide for drawing widgets in the grid, at least not in the way that 2.x did. Drawing so many widgets is often very expensive, so while it was supported, it was not encouraged for the performance issues it could cause.

    Check out https://developers.google.com/web-to...eUiCustomCells for additional documentation on building custom cells.
    Colin,

    I am looking for a solution to this as well. I understand the GWT cell designs and reasoning behind it, but I have a requirement that includes rendering grids with mostly text, and then one column of sparkline graphs. Graphs are a little beyond what i want to write up in SafeHtml.

    How would you suggest I approach it?

    I am currently doing this via a custom cell that basically builds the widget and then rips the HTML from it using "widget.getElement().getString()", and then returns that HTML in the cell's render method. I don't fully understand the widget's lifecycle yet but i am attempting to maintain the widget in cache and attach it to the element created in the DOM in order for it to handle events. Maintaining the widget in cache also allows me to create a finite number of them and reuse them by swapping out data on each render (i am only showing a max of 20 rows at a time) -- kind of like table cell reuse in iOS programming.

    I had this working for a previous chart library, but i'm swapping in GXT 3.x and need to give it a shot... I originally thought GXT handled widgets in grids. I would love to get your opinion on how i should handle this, or what i need to keep in mind. Will my approach work? Is there something i'm missing in handling widgets? Should i blow this all away and create my own table (and lose all the great features of GXT grids)?

    Here's the cell I created, i'm still in the process of wiring everything up so i have not tested it on GXT charts (this is written with charts in mind mainly -- AbstractClickableWidgetCellDto is an external dto that is part of the model so it can be updated and passed in via ValueProvider):


    Code:
    public class ClickableWidgetCell<W extends Widget, T extends AbstractClickableWidgetCellDto<W>>
            extends AbstractCell<T> {
    
    
        /**
         * Widget generator for use by this cell renderer. This used as a factory that is used when a new widget is needed.
         * 
         * @param <C>
         *            widget type returned
         */
        public interface ClickableWidgetCellWidgetFactory<C extends Widget> {
            C createWidget();
        }
    
    
        private static int uniqueKeyForWidgetID = 0;
    
    
        /**
         * map of widgets used in this column. Once created, the widget is cached so it doesn't need to be created again.
         */
        private Map<Object, WidgetForCell> reusableWidgetsMap = new HashMap<Object, WidgetForCell>();
    
    
        /**
         * This cell can be used as a non-clickable cell as well. this boolean will block the click and keydown events when
         * set to true via the setter.
         */
        private boolean blockClickableEvents = false;
    
    
        /**
         * Construct a new ClickableWidgetCell
         */
        public ClickableWidgetCell() {
            super("click", "keydown");
        }
    
    
        @Override
        public void onBrowserEvent(Context context, Element parent, T widgetDataDto, NativeEvent event,
                ValueUpdater<T> valueUpdater) {
            super.onBrowserEvent(context, parent, widgetDataDto, event, valueUpdater); // Handles user press actual
                                                                                           // ENTER key
            if ("click".equals(event.getType())) {
                onEnterKeyDown(context, parent, widgetDataDto, event, valueUpdater);
            }
        }
    
    
        @Override
        protected void onEnterKeyDown(Context context, Element parent, T widgetDataDto, NativeEvent event,
                ValueUpdater<T> valueUpdater) {
            if (!blockClickableEvents && valueUpdater != null) {
                valueUpdater.update(widgetDataDto);
            }
        }
    
    
        @Override
        public void setValue(Context context, Element parent, T value) {
            /*
             * Overridden for fine control of Widget updating
             */
            render(context, value, null, parent);
        }
    
    
        @Override
        public void render(Context context, final T widgetDataDto, final SafeHtmlBuilder sb) {
            render(context, widgetDataDto, sb, null);
        }
    
    
        public void render(Context context, final T widgetDataDto, final SafeHtmlBuilder sb, final Element parent) {
            /*
             * Widgets are reused so that their (expensive) creation is not performed each time. After creation, they are
             * attached and detached as needed and re-added to the page.
             */
            Object rowKey = context.getKey();
    
    
            /*
             * Either get the existing widget from the cache or create it.
             */
            WidgetForCell wfc = reusableWidgetsMap.get(rowKey);
            if (wfc == null) {
                // Create once and store.
                wfc = new WidgetForCell(widgetDataDto.getWidgetFactory().createWidget());
                reusableWidgetsMap.put(rowKey, wfc);
            }
            final WidgetForCell wfcAsFinal = wfc;
    
    
            /*
             * Update the widget with the new data. If it is not attached then add it to the parent element.
             */
            if (widgetDataDto != null) {
                widgetDataDto.updateWidget(wfcAsFinal.asWrappedWidget());
            }
    
    
            // If we have the parent, use the element directly instead of SafeHtml
            if (parent != null) {
                if (!wfcAsFinal.isAttached()) {
                    // Remove it from wherever it was and put it under this parent.
                    parent.appendChild(wfcAsFinal.getElement());
                }
            }
    
    
            // handle outsider widgets or components that use the SafeHtmlBuilder and call render directly.
            if (sb != null) {
                sb.append(new SafeHtml() {
                    private static final long serialVersionUID = 1L;
    
    
                    @Override
                    public String asString() {
                        return wfcAsFinal.getElement().getString();
                    }
                });
            }
    
    
            /*
             * Finally, if it is not attached, attach it.
             */
            if (!wfcAsFinal.isAttached()) {
                /*
                 * Create a scheduled command for AFTER the current GWT event loop completes (deferred). This triggers the
                 * code after the page is done loading. We are laying the HTML down in the page here, so we want to trigger
                 * onAttach in the handler afterwards.
                 */
                /*
                 * Wait for the current browser loop to complete before checking for the new element.
                 */
                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
                    @Override
                    public void execute() {
                        /*
                         * The browser event loop is completed so start checking for the new element.
                         */
                        Scheduler.get().scheduleIncremental(new RepeatingCommand() {
                            @Override
                            public boolean execute() {
                                if (wfcAsFinal.isAttached()) {
                                    // If the widget was attached via GWT, quit.
                                    return false;
                                }
    
    
                                Element e = wfcAsFinal.getElement();
                                if (e == null) {
                                    /*
                                     * Handle when SafeHtmlBuilder is used. Need to get the element created instead of the
                                     * Widget's element
                                     */
                                    e = DOM.getElementById(wfcAsFinal.getWidgetID());
                                }
    
    
                                if (DOMUtils.elementExistsInDOM(e)) {
                                    wfcAsFinal.attach();
                                    return false;
                                }
                                return true;
                            }
                        });
                    }
                });
            }
        }
    
    
        /**
         * If true, this will block the clickable events and this cell will simply render the widget without accepting
         * events.
         * 
         * @param blockClickableEvents
         */
        public void setBlockClickableEvents(boolean blockClickableEvents) {
            this.blockClickableEvents = blockClickableEvents;
        }
    
    
        /**
         * A widget for use in a cell. This exposes a couple of methods for the cell to access.
         * 
         * @param <WW>
         *            the type of widget encapsulated
         */
        private class WidgetForCell extends Composite {
    
    
            private JavaScriptObject detachEventHandlerFunction;
            private String id;
    
    
            public WidgetForCell(W widget) {
                initWidget(widget);
    
    
                if (uniqueKeyForWidgetID >= Integer.MAX_VALUE) {
                    // housekeeping.
                    uniqueKeyForWidgetID = 0;
                } else {
                    uniqueKeyForWidgetID++;
                }
    
    
                this.id = "WidgetForCell_" + uniqueKeyForWidgetID;
                this.getElement().setId(id);
            }
    
    
            public void attach() {
                // Now perform attach
                super.onAttach(); // containers handle calling children
                setupDetachEvent(getWidget().getElement());
            }
    
    
            public void detach() {
                removeDetachEventFromElement(getWidget().getElement(), detachEventHandlerFunction);
                super.onDetach(); // containers handle calling children
            }
    
    
            private native void setupDetachEvent(Element elem) /*-{
                var thisWidgetForCell = this;
                var detachHandler = function() {
                    thisWidgetForCell.@<packageName>.cells.widget.ClickableWidgetCell.WidgetForCell::detach()();
                };
    
    
                // Save a reference to this callback for removal... 
                thisWidgetForCell.@<packageName>.cells.widget.ClickableWidgetCell.WidgetForCell::detachEventHandlerFunction = detachHandler;
    
    
                // Register the event listener -- supported in all browsers (IE version 9+ only)
                elem.addEventListener("DOMNodeRemoved", detachHandler, false);
            }-*/;
    
    
            private native void removeDetachEventFromElement(Element elem, JavaScriptObject detachEventHandlerFunction) /*-{
                // remove event listener... 
                elem.removeEventListener("DOMNodeRemoved",
                        detachEventHandlerFunction, false);
            }-*/;
    
    
            /**
             * @return the specific widget wrapped here.
             */
            @SuppressWarnings("unchecked")
            public W asWrappedWidget() {
                return (W) super.getWidget();
            }
    
    
            public String getWidgetID() {
                return this.id;
            }
        }
    
    
    }