SharePoint und React – Das passt!
avatar

Für die Entwicklung clientseitiger Komponenten mit JavaScript gibt es eine Unmenge an Bibliotheken, die für die unterschiedlichsten Problemstellungen hilfreich sein können. Von A wie „aegis“ bis Z wie „zui“ ist für jeden JavaScript Entwickler etwas dabei. Alleine auf https://cdnjs.com/ gibt es derzeit 2730 unterschiedliche Bibliotheken zu Auswahl.

In dieser Vielfallt den Überblick zu behalten und das für das Projekt richtige Set an Bibliotheken zu finden, ist nicht immer ganz einfach.

Als Softwarearchitekt ist mir bei der Auswahl der JS Bibliotheken u.a. wichtig, wie nachhaltig diese ist und ob sie gut mit der übrigen Komponentenauswahl im Projekt harmoniert. Bei SharePoint Projekten ist eine genaue Prüfung der einzusetzenden Bibliotheken besonders wichtig, da SharePoint bekanntlich einige Eigenheiten aufweist (bspw. MDS, SOD, etc.), die nicht immer gut mit externen Komponenten harmonieren.

 

Warum React?

Die Art und Weise wie React arbeitet, ist für den Einsatz im SharePoint-Umfeld ideal. Das, was für eine React-Lösung in die Seite eingebunden werden muss, ist minimal und beschränkt sich auf die Referenz zur JS-Datei und die Zeile für die Einbettung. Der LifeCycle einer React Komponente harmoniert zudem sehr gut mit den SharePoint-eigenen Mechaniken. Somit kommt sich nichts in die Quere.

Während die meisten UI JavaScript Bibliotheken – wie AngularJS, Ember oder Knockout – durch Entwurfsmuster wie MVC oder MVVM eine Trennung von Logik und Ansicht anstreben, fährt React den Ansatz „Was zusammen gehört, wird auch zusammen definiert.“ Ich finde diesen Ansatz interessant und habe daher aus reinem Interesse eine einfache Beispielanwendung implementiert, die ich nachfolgend vorstellen möchte.

 

Das Mitarbeiter Board

Die entwickelte Lösung lädt alle Mitarbeitern (Site User) einer SharePoint Website und zeigt deren Daten in einer Visitenkarte an. Das ist doch nützlich. Smile 

image

Geht der Nutzer mit der Maus auf den blauen Bereich einer Karte, erhält er zusätzliche Informationen zum jeweiligen Kollegen.

image

Als kleines Zusatzfeature kann die Liste der gefundenen Site User gefiltert werden.

Der Aufruf der Komponente in einem Content Editor Webpart erfolgt – wie nachfolgend zu sehen – über “Hv.CardControl.Init(demoContent));” Der umliegende Code ist lediglich dazu da, um die die JS-Datei generisch auf der Seite zu zu referenzieren (via WSP ausrollbar) und um die erforderlichen SharePoint Skripte im Vorfelde zu laden.

Achtung: Der hier verwendete Code ist nicht MDS-fähig. (Dazu kommt vielleicht bei Zeiten auch noch ein Blogartikel. Smile )

 

...
.searchbar .search-btn:hover  {
                background-color:#fafafa;
                color:#207cca;
}

/******************End of Search bar ***************/

</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.js"></script>
<div id="demoContent"></div>

<script type="text/javascript">
    
    //load component logic
    function Load() {
        loadScript(_spPageContextInfo.webAbsoluteUrl + "/SiteAssets/component.js", function(){          
            SP.SOD.executeFunc('SP.js', 'SP.ClientContext', function () {
            // Make sure PeopleManager is available 
            SP.SOD.executeFunc('userprofile', 'SP.UserProfiles.PeopleManager', Hv.CardControl.Init('demoContent'));
          });        
        });
    }
   
    function loadScript(url, callback) {
        // Adding the script tag to the head as suggested before
        var head = document.getElementsByTagName('head')[0];
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = url;

        script.onload = callback;
        head.appendChild(script);
    }

    (function () {
        if (typeof (_spBodyOnLoadFunctions) === 'undefined' || _spBodyOnLoadFunctions === null) {
            return;
        }

        _spBodyOnLoadFunctions.push(function () {
            ExecuteOrDelayUntilScriptLoaded(Load(), 'SP.init.js');
        });
    })();
</script>

Der Code der eigentlichen React Komponente ist in der component.js gekapselt und nachfolgend zu sehen.

Ich habe im ersten Schritt bewusst auf den Einsatz von JSX verzichtet und muss sagen, dass ich auch mit der React nativen Syntax in der Render Methode gut klar komme. Liegt wohl am jahrelangen Background im Bereich Web Forms (CreateChildControls(…)). ;-P

 

"use strict";

var Hv = Hv || {};

Hv.CardControl = (function () {

  var Main = React.createClass({

    getInitialState: function () {     
     
      return { peoples: [],
               loading: false,
               filtertext:''              
             };
    },

    componentWillMount: function () {
     //Load  user profiles
     this.getUsersInfos();
    },
    render: function () {
      var component = this;

      var cards = this.state.peoples.map(function (people, i) {
        if(component.state.filtertext != ''){
            if(people.name.toLowerCase().includes(component.state.filtertext.toLowerCase())){
              return React.createElement(Card, { peopleData: people, key:i });
            }
        }
        else{
          return React.createElement(Card, { peopleData: people });
        }
      });
      
      var blocker= React.createElement('div', { className: 'blocker' }, null);
      var loader= React.createElement(Loader, { loading: component.state.loading });
      var searchBar= React.createElement(SearchBar, { onSearchBtnClick: component.handleSearchBtnclick });

      return React.createElement('div', { className: 'cards' }, searchBar, loader, cards, blocker);
    },

    handleSearchBtnclick: function (text) {
    
         this.setState({ filtertext: text });
          return false; 
    },

    getUsersInfos: function () {
     
      var component = this;
      component.setState({ loading: true });

      var clientContext = new SP.ClientContext.get_current();
      var messageText = '';
      var web = clientContext.get_web();
      var users = web.get_siteUsers();
      var peopleData = [];

      //load users with login names
      clientContext.load(users);
      clientContext.executeQueryAsync(function () {
        var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);
        var personsProperties = [];
        var profilePropertyNames = ["PreferredName", "PictureURL", "WorkPhone", "Department", "WorkEmail", "Manager"];
       
        for (var i = 0; i < users.get_count(); i++) {
            var user = users.getItemAtIndex(i);
            var userProfilePropertiesForUser = new SP.UserProfiles.UserProfilePropertiesForUser(clientContext, user.get_loginName(), profilePropertyNames);
            var userProfileProperties = peopleManager.getUserProfilePropertiesFor(userProfilePropertiesForUser);
            personsProperties.push(userProfileProperties);
        }

        //load user profile properties for detected users
        clientContext.load(userProfilePropertiesForUser);
        clientContext.executeQueryAsync(function () {
                var manager = '';
                var department = '';
                var imgUrl = '';
                var userEmailAddress = '';
                var phone = '';
                var name = '';
                                                                                                                                                                            
                for (var i = 0; i < personsProperties.length; i++) {

                  name = personsProperties[i][0];
                  department = personsProperties[i][3];
                  phone = personsProperties[i][2];
                  userEmailAddress = personsProperties[i][4];
                  imgUrl = personsProperties[i][1];
                  manager = personsProperties[i][5];

                  if (department != undefined && department != '') {
                        if (imgUrl == undefined || imgUrl == '') {
                          imgUrl = _spPageContextInfo.webAbsoluteUrl + "/SiteAssets/unbekannt.png";
                        }
                        
                          peopleData.push({
                                          name: name,
                                          phone: phone,
                                          email: userEmailAddress,
                                          manager: manager,
                                          department: department,
                                          imgUrl: imgUrl
                                        });                          
                      }//end 'if department is set'            
                }//end for user profiles 

                component.setState({ peoples: peopleData,
                                    loading: false });
              
          },
            function (sender, args) {
              //handle error
              alert('Profile info request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
              component.setState({ loading: false });          
          });//end of executeQueryAsync user login names
      },
          function (sender, args) {
            //handle error
            alert('User and login request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
            component.setState({ loading: false });          
     });// end of executeQueryAsync user login names
    }  // getUsersInfos
  }); //end of main

var SearchBar = React.createClass({
        onSearchBottonClick: function () {
        var text = this.refs.searchTxt.value;
        this.props.onSearchBtnClick(text);    
   
    },
      	render: function () {         
      	    var filterTextBox = React.createElement('input', { className: 'search-text', ref: 'searchTxt' }, null);
              var btn= React.createElement('button', { className: 'search-btn', onClick: this.onSearchBottonClick, type:'button'}, 'Go');
              return React.createElement('div', { className: 'searchbar' }, filterTextBox, btn);
          }
    });

    var Loader = React.createClass({
      	render: function () {
          if(this.props.loading === true){
              return React.createElement('div', { className: 'cards' }, 'Bitte warten...');
          }
          else{
              return React.createElement('div', { className: 'cards' }, null);
          }
      }
    });

  var Card = React.createClass({
    render: function () {

        var img = React.createElement('img', { className: 'person-img', src: this.props.peopleData.imgUrl }, null);

        var persName = React.createElement('span', null, this.props.peopleData.name);
        var rowtitel = React.createElement('div', { className: 'details-title' }, persName);

        var depLabel = React.createElement('div', { className: 'row-label' }, 'Abteilung: ');
        var depValue = React.createElement('div', { className: 'row-val' }, this.props.peopleData.department);
        var rowDepartment = React.createElement('div', { className: 'row' }, depLabel, depValue);

        var emailLabel = React.createElement('div', { className: 'row-label' }, 'Email: ');
        var emailLink = React.createElement('a', { href: 'mailto:' + this.props.peopleData.email }, this.props.peopleData.email);
        var emailValue = React.createElement('div', { className: 'row-val' }, emailLink);
        var rowEmail = React.createElement('div', { className: 'row' }, emailLabel, emailValue);

        var phoneLabel = React.createElement('div', { className: 'row-label' }, 'Telefon: ');
        var phoneValue = React.createElement('div', { className: 'row-val' }, this.props.peopleData.phone);
        var rowPhone = React.createElement('div', { className: 'row' }, phoneLabel, phoneValue);

        var managerlabel = React.createElement('div', { className: 'row-label' }, 'Manager: ');
        var managerValue = React.createElement('div', { className: 'row-val' }, this.props.peopleData.manager);
        var rowManager = React.createElement('div', { className: 'row' }, managerlabel, managerValue);

        var infoBody = React.createElement('div', { className: 'details-body' }, rowDepartment, rowEmail, rowPhone, rowManager);
        var rowInfo = React.createElement('div', { className: 'person-info' }, rowtitel, infoBody);

        return React.createElement('div', { className: 'person-card', key: this.props.key }, img, rowInfo);
    }
  });

  var init = function (parentElemId) {
    //Init method called to the render method of the component
    ReactDOM.render(React.createElement(Main, null), document.getElementById(parentElemId));
  }

  return {
    Init: init
  }

})();

 

React zwingt einen förmlich dazu sich etwas mehr Gedanken zum Grundgerüst der Lösung zu machen und so einen modularen Aufbau zu erreichen. Die einzelnen Bestandteile wie bspw. die Visitenkarten und die SearchBar können zudem beliebig wiederverwendet werden.

Ich hoffe der Artikel hat Euch gefallen!

Schreibe einen Kommentar