2

How do we attach a hover event to a fabric DetailsList in an SPFX web part?

I have a list of documents in a spfx web part displayed in a fabric DetailsList.

When a user hovers over an item I want to display the document using a fabric callout with an iframe pointing to the documents 'WOPI' Url, similar to the way that SharePoint search results do when you hover over a document.

  • is addEventListener failing on those DOM elements? – Danny '365CSI' Engelman May 23 '17 at 6:56
  • no, the question is more about how to get to the element and add the listener using the library. – russellg May 23 '17 at 10:44
  • I haven't seen the internals of the DetailsList, others like the ChoiceFieldGroup trigger a custom global event, but those are clicks, not mouseovers. And when you search the GitHub code there is nothing hover/mouseover to be found (at first sight) So I fear there are no mouseover hooks and you just have to go in, get the element and use vanilla JS to attach an event – Danny '365CSI' Engelman May 23 '17 at 11:59
  • yeah, i guess i could add some code in componentdidmount to find the elements and attach listeners. that sucks. i don't need it that bad. – russellg May 23 '17 at 21:52
4

I posted this question on the office-ui-fabric-react board a few days ago and DZearing got back to me within a few hours of my asking the question.

his response can be found at https://github.com/OfficeDev/office-ui-fabric-react/issues/1847. He recommended adding the following onRenderRow :

onRenderRow={ (props, defaultRender) => <div onMouseEnter={ () => console.log('hovering over: ' + props.item.title)}>{defaultRender(props)}</div> }

I did not know that the onRenderRow method got passed the default renderer.

So I created my details list like this:

<DetailsList
                layoutMode={DetailsListLayoutMode.fixedColumns}
                items={this.state.documents}
                onRenderRow={(props, defaultRender) => this.renderDocumentRow(props, defaultRender)}
                setKey="id"
                selectionMode={SelectionMode.none}
                columns={[
                  { key: "Edit", onRender: this.rendeDocumentAsLink, name: "", fieldName: "Title", minWidth: 20, },
                  { key: "title", name: "Request #", fieldName: "title", minWidth: 80, },

                ]}
              />

In the renderDocumentRow method, I wrapped the entire row withini a div that has mouseenter and mouserout events:

 public renderDocumentRow(props,defaultRender): JSX.Element {

    return (
      <div
        onMouseEnter={(event) => this.documentRowMouseEnter(props.item, event)}
        onMouseOut={(evemt) => this.documentRowMouseOut(props.item, event)}
      >
        {defaultRender(props)}
      </div>);
  }

The mousein and moouseout methods get the wopiframeurl for the document hovered over, and set some state variables so that I can render an iframe with the wpoisource:

public documentRowMouseEnter(trdocument: TRDocument, e: any) {
//mode passed to fetchDocumentWopiFrameURL: 0: view, 1: edit, 2: mobileView, 3: interactivePreview
    this.props.fetchDocumentWopiFrameURL(trdocument.id, 3).then(url => {
      if (!url || url === "") {
        url = trdocument.serverRalativeUrl;
      }
      this.state.documentCalloutIframeUrl=url;
      this.state.documentCalloutTarget = e.target;
      this.state.documentCalloutVisible = true;
      this.setState(this.state);

    });
  }
  public documentRowMouseOut(item: TRDocument, e: any) {

    this.state.documentCalloutTarget = null;
    this.state.documentCalloutVisible = false;
    this.setState(this.state);
    console.log("mouse exit for " + item.title);
  }

The render method has the list of documents in a left aligned div, and an iframe in a right aligned div . As a user hovers over a row on the left, a preview of the document is displayed on the right:

  <tabs.TabPanel>
        <div style={{ float: "left" }}> 
          <DetailsList
            layoutMode={DetailsListLayoutMode.fixedColumns}
            items={this.state.documents}
            onRenderRow={(props, defaultRender) => this.renderDocumentRow(props, defaultRender)}
            setKey="id"
            selectionMode={SelectionMode.none}
            columns={[
              { key: "Edit", onRender: this.rendeDocumentAsLink, name: "", fieldName: "Title", minWidth: 20, },
              { key: "title", name: "Request #", fieldName: "title", minWidth: 80, },

            ]}
          />
          <input type='file' id='uploadfile' onChange={e => { debugger; this.uploadFile(e) }} />
        </div>
        <div style={{ float: "right" }}>
        <DocumentIframe src={this.state.documentCalloutIframeUrl} />
        </div>
        <div style={{ clear: "both" }}></div>


      </tabs.TabPanel>

The display looks like this as I hover over the Office Brand Guidelines file on the left:enter image description here

It's a little slow to load the document images as I hover over the different documents(especially for larger documents), but it works great, is visually appealing and provides value to the users.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.