Embedding Tableau Server Views in C# Application Part 2: Web Edit

When a Tableau viz is embedded inside an iframe, the Web Edit button generates a pop-up window (or new tab) holding the Edit screen. When the user finishes editing, they should be sent back to the original page. This is fine in a modern web browser, but you don’t want them jumping out into a new Internet Explorer window when they are working within your embedded app.

Disabling Any Popup

Per Stack Overflow, you can stop any popups from an embedded WebBrowser control by simply adding the NewWindow event onto your control and doing the following


        private void webBrowser1_NewWindow(object sender, CancelEventArgs e)
        {

            e.Cancel = true;
        }

This solves the Web Edit popup, but it doesn’t redirect the link back into the original control.

Update: As explained in the updated Part 1 of the series, the better event to listen for is NewWindow3, which is available with the ExtendedWebBrowser class.

For a better experience, we skip NewWindow and go for NewWindow3

       private void webBrowser1_NewWindow(object sender, CancelEventArgs e)
        {

           // e.Cancel = true;
        }

        /*
         * The NewWindow3 object is triggered whenever a page tries to open a new window, which by default opens in a new IE window. 
         * NewWindow3 has property BStrURL which is the location to be loaded in the new window. If you cancel the action, you can
         * capture the location and reroute or choose a different set of actions.
         */
        private void webBrowser1_NewWindow3(object sender, NewWindow3EventArgs e)
        {
            
            //MessageBox.Show("We going somewhere", "Where we going?");
            //MessageBox.Show(e.BStrURL, "Going here");
            if (e.BStrURL.Contains("authoring")) {
                e.Cancel = true;
                MessageBox.Show(e.BStrURL, "Web Authoring Triggered");
                edit_mode = true;    
                webBrowser1.Navigate(e.BStrURL);
            }
            // This one needs the window to atually show up, maybe place in its own separate tab? But will that maintain session??
            else if (e.BStrURL.Contains("viewData"))
            {
                e.Cancel = true;
                webBrowser1.Navigate(e.BStrURL);
            }
            // For all of these, redirecting the URL back into current window causes the download dialog to appear successfully
            else if (
                        e.BStrURL.Contains("tempfile") ||
                        e.BStrURL.Contains("crosstab") ||
                        e.BStrURL.Contains("csv=true")
                    )
            {
                e.Cancel = true;
                webBrowser1.Navigate(e.BStrURL);
            }
            
        }
       private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
        {
            //   MessageBox.Show("We going somewhere WITHIN","Where we going?");
            if (edit_mode == true)
            {

                string url_string = e.Url.OriginalString;
                // This is what happens when Done or Save As happens
                if (url_string.Contains("views") ) {
                    MessageBox.Show(url_string, "Going back");
                    edit_mode = false;
                    string postData = String.Format("username={0}&view_location={1}", username, e.Url.OriginalString);
                    byte[] Post = Encoding.UTF8.GetBytes(postData);
                    string AdditionalHeaders = "Content-Type: application/x-www-form-urlencoded";

                    var page_location = "http://{servername}/portal/c_sharp_embed.php";
                    //webBrowser1.Navigate(page_location); // If using AD with automatic logon
                    webBrowser1.Navigate(page_location, "_self", Post, AdditionalHeaders);

                }
            }
        }

Each of the different features that generate a popup (web edit, see underlying data, export PNG, PDF, and crosstab) have a unique piece of their URL available via BStrURL that can be tested for. For example, when the URL contains ‘crosstab’, it is sufficient to simply redirect that URL directly back into the current webBrowser1 object using the Navigate method. Instead of a new IE instance, the regular Save As dialog appears and the file saves to disk.

For See Underlying Data, there should be some thought on how best to make it appear (perhaps a second window is appropriate?) Perhaps further updates to come

Web Edit

As you’ll see below, there is another solution for web edit where you trigger it within an iframe in the original embedded page rather than going to it as a link. But once you have the BStrURL property, you can handle everything entirely via the C# code as well. Notice the edit_mode boolean — this is a simple flag to give your application awareness of when it is on a web edit page. The flag is important because when you are on a web edit page, you want to capture the Navigating event and instead redirect back to the original embedding dynamic HTML page. Otherwise, by default pressing “Done” in web edit will go back to the Tableau Server UI, which you do not want to show.

The example Navigating event handler actually POSTs the location (WebBrowserNavigatingEventArgs.Url.OriginalString) back to the embedding page, which can be parsed so that if a Save As action occurs, you load the new version created via Save As rather than the original.

The following method works as well, and if you are trying to trigger from your own UI in the C# app, it’s the way to go (but you should still keep the edit_flag and have the Navigating event handler clean up, rather than trying to deal with onreadystatechange).

Embedding Web Edit as an Iframe

Web Edit can be triggered off ‘manually’ by going to locations with the following pattern:

http://TableauServer/t/site/authoring/wbname/dashboard/sheetname

Since we’ve already disabled the Edit button from the Tableau UI by disabling the popup, we should just hide the toolbar entirely and use our own UI (whether in the embedded page or in the C# app)


        // jQuery onload
        $( function() {
           
            $("#editViz").hide();
            var options = {
                // This function runs after the viz is loaded, so all additional API calls should generate from there
                onFirstInteractive : completeLoad,
                hideToolbar : true,
                height: '600px',
                width : '800px',
            };
            viz = new tableau.Viz( $("#tableauViz").get(0), vizUrl, options);
            //viz.addEventListener(tableauSoftware.TableauEventName.MARKS_SELECTION,getMarks);
           // viz.addEventListener(tableauSoftware.TableauEventName.FILTER_CHANGE, getFilter );
        });

One solution then is to load this location into another iframe in our embedded page, and then make that iframe visible and float it on top of the original Viz (you can also hide the div holding the original viz for a similar effect). This keeps everything on the same page, easily controlled. You can see that the ‘edit_viz’ div is hidden in the jQuery onload callback at the very beginning.

Trusted Authentication

The first time a trusted ticket is successfully used, it creates a Tableau session cookie that is used in the Web Edit view. This means the Web Edit link picks up who the logged in user is, and there is no need for sending any type of trusted URL requests.

Launching The Web Edit iframe

Loading the iframe for the Web Edit and making it visible is pretty trivial (i’m using jQuery here, and the PHP insert of $edit_url for the web edit location could be handled a number of ways, but I did want the web edit URL to be available in my JavaScript for use.


        function launch_edit(){
            $("#tableauViz").hide();
            var edit_location = <?php echo $edit_url; ?>;
            edit_iframe = document.createElement('iframe');
            edit_iframe.src = edit_location;
            edit_iframe.height = 800;
            edit_iframe.width = 1200;
            document.body.appendChild(edit_iframe);
            
            $("#edit_viz").show();
            edit_iframe.onload = iframe_exit_listen; // Not necessary with Navigating event listener in the C#
            edit_hidden = false;
            
        }
        // Not necessary when listening to Navigating with edit_mode flag
        function iframe_exit_listen(){
            edit_iframe.onreadystatechange =  iframe_change;
           // edit_iframe.onunload =  iframe_change; // onunload does not work on an iframe
        }
        // Not necessary when listening to Navigating event with edit_mode flag
        function iframe_change(){
                if (edit_hidden == false){
                    $("#edit_viz").hide();
                    $(edit_iframe).hide();
                    edit_hidden = true;
                    //viz.refreshDataAsync();
                    //$("#tableauViz").show();
                    location.reload(true);
                }
        }

Update: As mentioned earlier (and in the code), listening in on the Navigating event is a cleaner way of handling the exit here, but this should still work.

iframe’s have funny rules about what you can do with them, particularly when they originate from a different domain. On iframe can have an onload event, and it will fire off once the iframe has come into existence in the DOM. But onunload won’t do anything if assigned on an iframe; the iframe will go about its business moving to another page without firing off the event up to its parent window.

To get around this, the onload event then fires the  iframe_exit_listen function which assigns another function to the onreadystatechange event. What is onreadystatechange? It is an event that is only available in Internet Explorer (we’re in luck because the WebControl is actually IE underneath), and it happens to fire off when an iframe starts to change from one location to the next. This is exactly what happens when a user press the ‘Done’ button in the Web Edit page. So using this code,  the iframe_change function kicks off as soon as the user presses the Done button, hides the iframe containing the Web Edit page, and then reloads the whole original embedded page. This causes the page to reload with all of the user’s changes from Web Edit now visible.

This does point them back to the original view even if they do Save As. The Web Edit iframe itself will actually be loading to the location wherever the Save As was (I’ve just hidden the div and the iframe but not destroyed them, so potentially you could pull that value out and send it around somewhere else to load the user to the newly saved version rather than the original rather than doing a location.reload()

2 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s