Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Canvas resizebar problem with iframes

    Hi,

    We have an application with a quite typical split pane layout and we are setting showResizeBar(true) to make the pane width and heights resizeable for user. We are using some iframe-based components in our panes (for example rich text editors which render their body content using iframes).

    This has the effect that the panes' resize bars won't work quite well anymore as when you start to drag the bar the mouse 'slips' over the iframe and the resize bar loses focus. Basicly the resizing works only when you drag the resize bar very carefully so that the mouse cursor does not have time to jump over to the iframe.

    Any ideas if this can be avoided somehow ? The fix I think would be that on mouse down event on a resize bar an opaque mask would be appended over the page which would make sure that the events aren't accidentally sent to the iframe.

    Using smartgwt 3.1p lgpl NIGHTLY-2013-02-16.

    br,
    Marko

    #2
    Marko,

    Have you found a solution to this? I'm trying to work the same problem.

    Thanks.

    Comment


      #3
      No, sorry - problem still exists.

      Marko

      Comment


        #4
        I have a solution that works for me. It's a bit complex, involves many working parts, and I'm sure there's ways to improve. But it works for me and now I'm on to other things.

        The solution involves
        1. Extending isc objects
        2. JSNI methods
        3. Custom events

        Continue reading only if you really want to get this working.

        First: I agreed with your approach on throwing up glass panels over the content so that the iframe content would not capture the mouse movements. I started with that premise.

        Second: For this to work, we need to know when the user starts moving the resizebar. The first thing I looked for was whether they exposed the SplitBar object (the one generated when setShowResizeBar(true) is used). If we had a handle on that, we could add our own handler using addDragRepositionStartHandler. Sadly, this doesn't seem to be exposed to via the SmartGWT interface.

        Third: What we can do is define out own class to be used as the splitbar and then rewrite the dragStart and dragEnd methods.

        Here's the solution I used
        1. I defined my own splitbar class (extends the Snapbar javascript object)

        Code:
        <script type="text/javascript">
        			isc.defineClass("MySplitbar", "Snapbar").addMethods({
        			}, {
        				dragStart: function() {
        					if (this.showDown)
        						this.setState("Down"); // note: case sensitive
        					this.bringToFront(); // so we aren't occluded by what we will drag resize
        					icf_dragStartDualPane();
        				},
        				dragStop: function() {
        					if (this.showDown)
        						this.setState("");
        					this.finishTargetResize(this.target, !this.vertical, this.resizeInRealTime);			
        					icf_dragStopDualPane();
        				}
        			});
        		</script>
        I put this at the end of my bootstrap html AFTER

        Code:
        <script type="text/javascript" language="javascript" src="app/app.nocache.js"></script>
        Notice the two javascript methods icf_dragStartDualPane and icf_dragStopDualPane. These are JSNI methods that I injected in my EntryPoint. More on these in a bit.

        What I originally wanted to do was just append my icf_* functions to the dragStart and dragStop methods. I couldn't get that to work. So I tracked down the code that SmartGWT currently has in those functions, replicated it in my Extension object and then added my icf_* calls.

        2. On the layout that contains the member that shows the resizebar, you need to tell it to use your class

        Code:
        Layout layout = new VLayout();
        layout.setResizeBarClass("MySplitbar");
        
        Canvas canvas1 = new Canvas();
        canvas1.setShowResizeBar(true);
        Canvas canvas2 = new Canvas();
        
        layout.addMember(canvas1);
        layout.addMember(canvas2);
        3. In my entrypoint code, I create JSNI methods. These are the icf_* methods that the MySplitBar class references (in #1).

        Code:
        private native void addEventsSupport() /*-{
        	 $wnd.icf_dragStartDualPane = function () {
        	 @com.test.client.AppEntryPoint::dualPaneDragEvent(Ljava/lang/String;)('dragStart');
        	 };
        	 $wnd.icf_dragStopDualPane = function () {
        	 @com.test.client.AppEntryPoint::dualPaneDragEvent(Ljava/lang/String;)('dragStop');
        	 };
        	 }-*/;
        4. Finally, I create the methods that those JSNI functions call. Hopefully, you've already taken advantage of custom events and can create your own.

        Code:
        public static void dualPaneDragEvent(String action)
        	{
                     //Use the eventbus to fire custom event
        	}
        5. On your original layout, add a listener to handle that custom event you fired off in #4. In that handler, you can throw up your glass panels if event action is 'dragStart' and hide them again if event action is 'dragStop'

        The final result is pretty slick. Even going through JSNI, the glass panels come up pretty fast (tested in Firefox and IE). I'm now able to drag the resize bar even over panels that contain iframes.
        Last edited by kjmoroney; 1 Jun 2013, 09:08.

        Comment


          #5
          Sorry you had to go through this much trouble. We intended to revisit whether the framework can automatically handle this and just didn't get to it yet.

          If you'd like a cleaner approach, you can actually access the auto-generated resize bars - they are children (not members) of the Layout and will be there if you iterate through the children array after draw. You should then be able to add normal dragStart/dragEnd handlers to them to create your "glass", which gets rid of the need to do any JSNI or know any internals.

          Something that would help us add this at the framework level sooner - what browsers did you test? Especially how far back in IE?

          Comment


            #6
            For completeness - in 4.0, resizeBarClass can now be the fully qualified name of a class created in Java. So that also eliminates the need to find resizeBars in the children array after draw().

            Comment


              #7
              Excellent! Thanks for the update. I should have realized the best way to access the resizebar was after draw. I'll update my code to use that method. It's definitively cleaner and reduces complexity in my code (always a good thing). It's also good to hear 4.0 has an even better solution. Unfortunately, I'm still on 3.0p and cannot upgrade yet.

              With regard to browser testing, I've only tested my solution using Firefox 17 and IE8.

              Comment


                #8
                Update: I implemented as suggested. Unfortunately, it seems my addDragStart/addDragStop override the default functionality. The glass objects do indeed appear and I'm able to move the resize bar easily, but when I release it, the panes do not resize as expected.

                Note: I had to compare the JsObj output to find the Snapbar. I tried the java instanceof but could not find it using instanceof Splitbar or Snapbar.

                Here's the code I put in the onDraw of layout

                Code:
                for (Canvas c : layout.getChildren())
                		{
                			if (c.toString().startsWith("Snapbar"))
                			{
                				c.addDragStartHandler(new DragStartHandler()
                				{
                					@Override
                					public void onDragStart(DragStartEvent event)
                					{						
                						mask1.show();
                						mask2.show();
                					}
                				});
                				c.addDragStopHandler(new DragStopHandler()
                				{
                					@Override
                					public void onDragStop(DragStopEvent event)
                					{
                						mask1.hide();
                						mask2.hide();
                					}
                				});
                			}
                Any thoughts?
                Last edited by kjmoroney; 30 May 2013, 07:57.

                Comment


                  #9
                  We know what the issue is with not having the appropriate Snapbar / Splitbar SGWT class returned and we'll be fixing it.

                  It's true that right now, adding those event handlers to the Splitbar will clobber rather than extend the built-in behavior. We're thinking about making that not the default.

                  In the meantime, your Splitbar subclass is probably the cleanest approach, however, it should not be necessary to do anything more than call this.Super("dragStart", arguments) to preserve the built-in behavior (followed by your own code). And even if this was an issue somehow, using Class.observe() would be another way to get this notification; there shouldn't be a need to call a GWT-generated method. This again puts your solution back in the realm of purely documented APIs.

                  However, bigger picture, if you had used an HTMLFlow/HTMLPane with contentsType:"page", this masking during drag would be automatic - was that not feasible for whatever you're building?

                  Comment


                    #10
                    Thanks again for the quick response. I'll make the suggested changes. I'll admit my javascript skills are not very sharp. Makes sense that javascript has a super just like java.

                    contentsType:"page" is not an option for some of our widget funtionality. In some cases, iframe type functionality simply loads much faster. Depending on the content of the fragment, DOM manipulation is slower (and in Internet Explorer case much slower). Additionally, iframes provide a clean way of isolating external JS and CSS eliminating the potential of SmartClient conflict. Bottom line, we use FRAGMENT in some cases and PAGE in others. This splitbar fix allows for the both cases to be used without providing different user experiences for each.

                    Another related question: The fix in this thread has to do with throwing up glass panes so that GWT can continue tracking mouse movements (and thus allow the splitbar drag to continue). Another option is to inject a mouseevent listener into the iframe document onload. The mousemove listener could then call a parent.SOME_JSNI_FUNCTION with the mouse coordinates relative to the top WINDOW. The JSNI function would be responsible for programatically generate a mousemove event for SmartGWT's sake. This functionality would eliminate the need for glass panes.

                    I already have the above feature working (needed it for other reasons) with one exception. I use my own object to track the mouse position. For it to work for this use case, I really need to generate a "real" mousemove event in SmartGWT. I couldn't find a way to programatically generate a mousemove event. I think jQuery has a mousemove mimic method, but before I go down that path, I'm wondering if SmartGWT has something too. Does SmartGWT have an API that would let me do that?

                    I ask because, while my solution does work, knowing how to do that would collapse my support code down to only a handful of JSNI methods.

                    Comment


                      #11
                      To complete the thread, for the benefit of others looking for this solution, here's my final Splitbar subclass. It works great.

                      Code:
                      <script type="text/javascript">
                      	isc.defineClass("MySplitbar", "Snapbar").addMethods({
                      		setDragStartDistance:1
                      	}, {
                      	dragStart: function(e) {
                      	            this.Super("dragStart", e);
                      	            icf_dragStartDualPane();
                      	},
                      	dragStop: function(e) {
                      	       this.Super("dragStop", e);
                      	       icf_dragStopDualPane();
                      	}
                      	});
                      </script>

                      Comment


                        #12
                        Originally posted by kjmoroney View Post
                        Makes sense that javascript has a super just like java.
                        It doesn't, we had to write one in JavaScript (and you wouldn't believe the number of edge cases that must be handled).

                        Originally posted by kjmoroney View Post
                        contentsType:"page" is not an option for some of our widget funtionality. In some cases, iframe type functionality simply loads much faster. Depending on the content of the fragment, DOM manipulation is slower (and in Internet Explorer case much slower). Additionally, iframes provide a clean way of isolating external JS and CSS eliminating the potential of SmartClient conflict. Bottom line, we use FRAGMENT in some cases and PAGE in others. This splitbar fix allows for the both cases to be used without providing different user experiences for each.
                        Didn't quite follow this.. contentsType:"page" writes an iframe, and automatically masks during drag. So are you saying you're using contentsType:"fragment", but in your fragment you're creating your own iframe? That seems unnecessary, and would disable automatic handling of dragging over iframes, as you've observed.

                        Originally posted by kjmoroney View Post
                        Another related question: The fix in this thread has to do with throwing up glass panes so that GWT can continue tracking mouse movements (and thus allow the splitbar drag to continue). Another option is to inject a mouseevent listener into the iframe document onload.
                        ...
                        I couldn't find a way to programatically generate a mousemove event.
                        Definitely would not recommend going down this path relative to glass panes (or to the possibility of eliminating your workaround code entirely mentioned above).

                        Cross-frame calls are one of the places where the browser bug thicket is densest. Generating synthetic events is another, and no, we don't currently have a SmartGWT API; you have to do it through the low-level DOM approach, which is presumably what JQuery is wrapping with helper methods.

                        Comment


                          #13
                          1. With regard to Super: That makes me feel better. I was like "How did I not know there was a super in javascript!". Thanks for your efforts, they made my job easier.

                          2. With regard to using "fragment" and having our own iframe. For the functionality we were using, we really needed addContentLoadedHandler functionality. As it is documented that that feature is not supported with contentsType:"page", we introduced our own iframe and put an onload JSNI handler on it. That decision was made a while back and some of the issues we are handling now stem from that one decision.

                          3. With regard to cross-frame calls: Thanks for your advice. That will save me from spending time going down a dead path.
                          Last edited by kjmoroney; 3 Jun 2013, 05:43.

                          Comment


                            #14
                            Thanks a ton kjmoroney! Implemented the masking with your initial instructions and it's working fine.

                            Got it working now in ie8 (in compatibility mode), ie9, ie10, safari/chrome and firefox.

                            br,
                            Marko

                            Comment


                              #15
                              For easier handling this issue Canvas.useDragMask property was added, so when it enabled, a backmask is automatically created for the dragMoveTarget on the fly to avoid burnthrough. Also Canvas.dragMaskType property was added to control what kind of mask to use. It could be one of three types:
                              "div" - creates an element with ordinary HTML content that will block events
                              "iframe" - creates an iframe with empty content
                              "hide" - hides the contents of this widget temporarily

                              Comment

                              Working...
                              X