193 Lotus blogs updated hourly. Who will post next? Home | Blogs | Search | About 
 
Latest 7 Posts
Goodbye Evernote
Tue, Jan 17th 2017 7
Merry Christmas!
Sun, Dec 25th 2016 9
Visual Studio Code Editor
Fri, Nov 11th 2016 9
Work with Rich Text from DDS
Fri, Oct 21st 2016 6
A Polymer Avatar component
Wed, Oct 12th 2016 7
MWLUG Recap
Sat, Aug 20th 2016 7
Red Pill is the MWLUG Sponsor of the Week
Fri, Aug 12th 2016 11
Top 10
Red Pill is the MWLUG Sponsor of the Week
Fri, Aug 12th 2016 11
Polymer app-layout Elements
Fri, Jul 1st 2016 10
1.5 Years with Polymer Web Components
Sun, May 29th 2016 9
IBM Please fix Domino authentication
Tue, May 24th 2016 9
Domino svg support
Fri, Aug 12th 2016 9
Visual Studio Code Editor
Fri, Nov 11th 2016 9
Merry Christmas!
Sun, Dec 25th 2016 9
Setting up a Polymer development environment
Fri, Jul 29th 2016 8
Re-usability is the Goal!
Tue, Jun 14th 2016 7
MWLUG Recap
Sat, Aug 20th 2016 7


A Polymer Avatar component
Twitter Google+ Facebook LinkedIn Addthis Email Gmail Flipboard Reddit Tumblr WhatsApp StumbleUpon Yammer Evernote Delicious
Keith Strickland    

At MWLUG a lot of people seemed surprised that I would create a component just for an avatar. My response to that is why wouldn’t you build an avatar component? So, in this post I’m going to show you how to build one for your applications.

Let’s start out with the use case. We want to show an avatar for a person in various locations throughout our application. This avatar should do a few different things:

  • Should show a picture of a person if one is attached to their person document
  • If no picture is available and the person is in the domino directory should show the first letter of their first name and have a background color that is the same throughout the system
  • If the person is not in the domino directory just show a person icon
  • Should be able to click the avatar and it should do something (i.e. navigate to a person profile or somewhere else)
  • We’ll want to hand this component a canonical name and tell it how to get to our person document
  • We don’t want to have to install anything so we should use DDS

I’ll post the code here but I’m not going to explain everything, just the interesting bits.

<link rel="import" href="../polymer/polymer.html">

<link rel="import" href="../iron-ajax/iron-ajax.html">
<link rel="import" href="../iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../iron-icons/social-icons.html">
<link rel="import" href="../iron-image/iron-image.html">

<link rel="import" href="tinycolor.html">


<dom-module id="now-avatar">
  <template>
    <style is="custom-style" include="iron-flex iron-flex-alignment">
      :host {
        display: inline-block;
        position: relative;
        width: var(--now-avatar-height-width, 40px);
        height: var(--now-avatar-height-width, 40px);
        border: var(--now-avatar-border-size, 1px) solid var(--now-avatar-border-color, #afafaf);
        border-radius: 50%;
        @apply(--now-avatar);
      }
      :host:hover {
        cursor: pointer;
        @apply(--now-avatar-hover);
      }
      .avatarContainer {
        height: 100%;
        width: 100%;
      }
      .avatarImageContainer {
        height: inherit;
        width: inherit;
      }
      .avatarImage {
        border-radius: 50%;
        height: inherit;
        width: inherit;
        line-height: var(--now-avatar-height-width, 40px);
      }
      .avatarIcon {
        display: inline;
        line-height: var(--now-avatar-height-width, 40px);
      }
      .avatarLetterContainer {
        width: inherit;
        text-align: center;
      }
      .avatarLetter {
        line-height: var(--now-avatar-height-width, 40px);
      }
    </style>
    <!-- Lookup the user name in the ($Users) view of the domino directory -->
    <iron-ajax
      id="viewPeopleAjax"
      url="{{ddsViewUrl}}"
      handle-as="json"
      last-response="{{_viewPerson}}"
      last-error="{{_fetchViewPersonError}}"
      verbose>
    </iron-ajax>
    <!-- Fetch the user once they are found in the ($Users) view -->
    <iron-ajax
      id="personAjax"
      url="{{ddsPersonUrl}}"
      handle-as="json"
      on-response="_onPersonChange"
      last-error="{{_fetchPersonError}}"
      verbose>
    </iron-ajax>
    <div id="avatarContainer" class="avatarContainer layout horizontal">
      <!-- The letter -->
      <span
        class="self-center avatarLetterContainer"
        hidden$="{{_hideLetter}}">
        <span id="avatarLetter" class="avatarLetter center">{{avatarLetter}}</span>
      </span>
      <!-- The icon -->
      <span
        class="self-center avatarIconContainer"
        hidden$="{{_hideIcon}}">
        <iron-icon id="avatarIcon" class="avatarIcon" icon="social:person"></iron-icon>
      </span>
      <!-- The image -->
      <span
        class="self-center avatarImageContainer"
        hidden$="{{_hideImage}}">
        <iron-image
          sizing="cover"
          src="{{_photoUrl}}"
          hidden$="{{_hideImage}}"
          class="avatarImage fit"
          error="{{_fetchImageError}}"
          fade>
        </iron-image>
      </span>
    </div>
  </template>
  <script>
Polymer({
  is: 'now-avatar',
  properties: {
    /**
     * The person's canonical name. Not needed if you provide a photoUrl
     * @type {String}
     */
    name: {
      type: String,
      observer: '_onNameChange'
    },
    /**
     * The person document retrieved from the domino directory
     * @type {Object}
     */
    person: {
      type: Object
    },
    /**
     * Person retrieved from the view query
     * @type {Array}
     */
    _viewPerson: {
      type: Array,
      observer: '_onViewPersonChange'
    },
    /**
     * The DDS URL to the '($Users)' view in the domino directory formatted like:
     * 'http://domino.server/names.nsf/api/data/collections/unid/<ViewUNID>'
     * NOTE: With this one we need to include the protocol and host name
     * @type {String}
     */
    ddsViewUrl: {
      type: String,
      observer: '_onDdsViewUrlChange'
    },
    /**
     * The DDS Url for documents in the database. Should be formatted like:
     * '/names.nsf/api/data/documents/unid/<PersonDocUNID>'
     * NOTE: DO NOT include the protocol and host name
     * @type {String}
     */
    ddsPersonUrl: String,
    /**
     * The url to navigate to when the avatar is clicked. Will open a new tab
     * @type {String}
     */
    clickUrl: String,
    /**
     * The protocol and host name to the DDS Url formatted like:
     * 'http://domino.server.name'
     * @type {String}
     */
    ddsHostName: String,
    /**
     * The name of the field in the Domino Directory person document where user photos are uploaded
     * @type {String}
     */
    photoField: String,
    /**
     * The name of the field in the Domino Directory person document where a photo url is stored
     * @type {String}
     */
    photoUrlField: String,
    /**
     * A url where a photo can be used. If you use this, you don't need to specify a name or DDS Urls
     * @type {String}
     */
    photoUrl: {
      type: String,
      observer: '_onPhotoUrlChange'
    },
    /**
     * This is the property that iron-image will use to fetch the image
     * @type {String}
     */
    _photoUrl: String,
    /**
     * The First letter of the user's common name
     * @type {String}
     */
    avatarLetter: String,
    /**
     * True if the letter should be hidden
     * @type {Boolean}
     */
    _hideLetter: {
      type: Boolean,
      value: true
    },
    /**
     * True if the icon should be hidden
     * @type {Boolean}
     */
    _hideIcon: {
      type: Boolean,
      value: false
    },
    /**
     * True if the image should be hidden
     * @type {Boolean}
     */
    _hideImage: {
      type: Boolean,
      value: true
    },
    /**
     * Object when an error occurs fetching a person
     * @type {Object}
     */
    _fetchPersonError: {
      type: Object,
      observer: '_onFetchPersonError'
    },
    /**
     * Object when an error occurs fetching the view entries
     * @type {Object}
     */
    _fetchViewPersonError: {
      type: Object,
      observer: '_onFetchViewPersonError'
    },
    /**
     * Object when an error occurs fetching an image
     * @type {Object}
     */
    _fetchImageError: {
      type: Object,
      observer: '_onFetchImageError'
    }
  },
  listeners: {
    'tap': '_onTap'
  },
  /**
   * Produce an event when an error occurs fetching the image
   * @param  {Object} newVal The error
   * @param  {Object} oldVal The old error
   * @event now-avatar-image-error
   */
  _onFetchImageError: function(newVal, oldVal) {
    this.fire('now-avatar-image-error', newVal);
  },
  /**
   * Produce an event when an error occurs fetching the a person from the view
   * @param  {Object} newVal The error
   * @param  {Object} oldVal The old error
   * @event now-avatar-view-person-error
   */
  _onFetchViewPersonError: function(newVal, oldVal) {
    this.fire('now-avatar-view-person-error', newVal);
  },
  /**
   * Produce an event when an error occurs fetching a person document
   * @param  {Object} newVal The error
   * @param  {Object} oldVal The old error
   * @event now-avatar-person-error
   */
  _onFetchPersonError: function(newVal, oldVal) {
    this.fire('now-avatar-person-error', newVal);
  },
  /**
   * Sets which items are hidden and shown
   * @param {Boolean} hideIcon   True to hide the icon
   * @param {Boolean} hideLetter True to hide the letter
   * @param {Boolean} hideImage  True to hide the image
   */
  _setHiddenContent: function(hideIcon, hideLetter, hideImage) {
    this._hideIcon = hideIcon;
    this._hideLetter = hideLetter;
    this._hideImage = hideImage;
    this._setStyles();
  },
  /**
   * Fired when the name changes, sets the letter and and styles
   * @param {String} newVal The new value for the name
   */
  _onNameChange: function(newVal) {
    this.avatarLetter = this.name ? this.name.substring(4, 3).toUpperCase() : null;
    if (newVal) {
      this._setHiddenContent(true, false, true);
    }else {
      this._setHiddenContent(false, true, true);
    }
  },
  /**
   * Fires when the ddsViewUrl changes
   * @param  {String} newVal The new name
   * @param  {String} oldVal The old name
   */
  _onDdsViewUrlChange: function(newVal) {
    if (this.name && this.ddsViewUrl && this.ddsViewUrl !== '') {
      this._setHiddenContent(true, false, true);
      var ajax = this.$.viewPeopleAjax;
      ajax.params = {
        sortcolumn: 'FullName',
        keys: this.name
      };
      ajax.generateRequest();
    }else if (this.name && !this.ddsViewUrl) {
      this._setHiddenContent(true, false, true);
    }
  },
  /**
   * Fired when the person changes
   * @param  {Object} newVal The new person
   * @param  {Object} oldVal The old person
   */
  _onPersonChange: function(evt, detail) {
    //console.log(this.is, '_onPersonChange', arguments);
    var person = detail.response;
    this.person = person;
    if (person) {
      if (this.photoUrl) {
        this._photoUrl = this.photoUrl;
        this._setHiddenContent(true, true, false);
      }else if (this.photoField) {
        var mime = person[this.photoField];
        if (person[this.photoField]) {
          var content = person[this.photoField].content;
          for (var key in content) {
            var contentType = content[key].contentType;
            if (contentType.indexOf('image') > -1) {
              var contentTypeArr = contentType.split('"');
              var fileName = contentTypeArr[1];
              this._photoUrl = this.ddsHostName + '/names.nsf/0/' + person['@unid'] + '/$File/' + fileName;
              this._setHiddenContent(true, true, false);
              break;
            }
          }
        }
      }else {
        this._setHiddenContent(true, false, true);
      }
    }else {
      this._setHiddenContent(false, true, true);
    }
    if (this._hideIcon && !this._hideLetter && this._hideImage && this.name) {
      this._setHiddenContent(true, false, true);
    }
  },
  /**
   * Sets the background color, text color and line height
   */
  _setStyles: function() {
    if (this.name) {
      var bgColor = this.getBackgroundColor();
      var contrastColor = this.getContrastingColor(bgColor);
      this.style.background = bgColor;
      this.style.color = contrastColor;
    }
  },
  /**
   * Fires when the viewPerson property changes. Fetches the person
   * @param  {Array} newVal The new array from the query of the view
   * @param  {Array} oldVal The old array
   */
  _onViewPersonChange: function(newVal, oldVal) {
    //console.log(this.is, '_onViewPersonChange', arguments);
    if (newVal && newVal.length > 0) {
      var personEntry = newVal[0];
      this.ddsPersonUrl = this.ddsHostName + personEntry['@link'].href;
      var ajax = this.$.personAjax;
      ajax.generateRequest();
    }else {
      this._setHiddenContent(false, true, true);
    }
  },
  /**
   * Fired when the avatar is tapped. Provides a detail object with
   * person, this element and name
   * @param  {Event} evt The event object
   * @event now-avatar-tapped
   */
  _onTap: function(evt) {
    //console.log(this.is, '_onTap', arguments);
    if (this.clickUrl) {
      window.open(this.clickUrl, '_blank');
    }
    var detailObj = {
      person: this.person,
      elem: this,
      name: this.name
    };
    this.fire('now-avatar-tapped', detailObj);
  },
  /**
   * Get an avatar background color based on the canonical name
   * @return {String}      Hex color
   */
  getBackgroundColor: function() {
    var hash = 0;
    if (this.name) {
      var name = this.name;
      for (var i = 0; i < name.length; i++) {
        hash = name.charCodeAt(i) + ((hash << 5) - hash);
      }
    }
    var colour = '#';
    for (var j = 0; j < 3; j++) {
      var value = (hash >> (j * 8)) & 0xFF;
      var valueStr = value.toString(16);
      var hexColor = ('00' + valueStr).substr(-2);
      colour += hexColor;
    }
    return colour;
  },
  /**
   * Get a contrasting color that will show up on the avatar
   * @param  {String} colour The color to find the contrasint color from
   * @return {String}        css 'color' property value
   */
  getContrastingColor: function(colour) {
    return tinycolor(colour).isDark() ? 'white' : 'black';
  },
  /**
   * Fired when the photoUrl changes
   * @param  {String} newVal The photo url
   * @param  {String} oldVal The old photo url
   */
  _onPhotoUrlChange: function(newVal, oldVal) {
    if (newVal) {
      this._photoUrl = newVal;
      this._setHiddenContent(true,true,false);
    }
  }
});
  </script>
</dom-module>

Quite a bit going on here. The gist of this thing is to check what properties we have defined and make decisions based on those properties so we know what to show (i.e. A letter, icon or image). Also, perform any requests to the server we may need to determine what to show. So the flow through this is as follows:

  1. Look at the properties provided
  2. If no properties defined, just show an icon
  3. If only a name is provided
    1. display the avatar with a background color unique to that name along with the first letter of the common name
  4. If a name and DDS url information is provided
    1. Make a request to the server using the URL provided. We use the ($Users) view so we have to pass the name as the key in order to just return that entry
    2. Once we get that view entry, get the UNID of the document
      1. If we don’t find the person, show an icon
    3. Make another request to get the person document using the UNID from the view entry
    4. Set the “person” property to the response we got back from the server
      1. If the photoField property was defined, get the image from that field
      2. If the photoUrl property was defined, get the image from that url
      3. Show the relevant image in the avatar
  5. If no name is defined and only the photoUrl property is defined, show the photo from the url

The only really interesting bits here are the observers which fire when a value changes. In all my previous component examples I did not show an observer. This functionality is very powerful and allows you to make decisions when a value changes based on the actual value.

The other interesting bit is how the background color is determined. We make a hash from the name (that way it’s the same every time) and then we create a hexadecimal color string based on the hash. This ensures we get the same color for the same name every time, and in theory every name will be it’s own unique color. The biggest problem with this though is how do you select the color of text or of the icon so it’s visible on the auto-generated background color? I elected to use TinyColor. This is a small javascript library for working with colors that seems to be pretty performant.

Now to use this component we just define it in HTML.

<now-avatar
  dds-host-name="http://10.211.55.9"
  dds-view-url="http://10.211.55.9/names.nsf/api/data/collections/unid/912366901F00A457852561C20069B844"
  name="CN=Keith Strickland/O=REDPILL"
  photo-field="UserPhoto">
</now-avatar>

<now-avatar
  class="styledNowAvatar"
  name="CN=Keith Strickland/O=REDPILL">
</now-avatar>

<now-avatar
  photo-url="http://www.gravatar.com/avatar/2b14efcb21ba7256e13a981392832c84?d=404">
</now-avatar>

<now-avatar
  click-url="http://redpillnow.com">
</now-avatar>

There you have it, a rather robust avatar component with some smarts built into it so it know what to do with the information provided. If you wish to use this component visit it’s repository on GitHub and to see the docs and demo, visit my github host account. Now on the demo page, you’ll probably see failed requests because it can’t get to my domino directory, but I hope you’ll get the idea.

Until next time… Happy Coding!

 

 

Share This:



---------------------
http://keithstric.me/2016/10/12/a-polymer-avatar-component/
Oct 12, 2016
8 hits



Recent Blog Posts
7
Goodbye Evernote
Tue, Jan 17th 2017 5:22p   Keith Strickland
I’ve been using Evernote for a few years now and have enjoyed it’s feature set and the ability to plan and document a complex project (namely home/shop projects) with shopping lists, ideas, etc. But recently every time I attempt to use Evernote to create a quick note or maybe just jot something down, I’m presented with a request to upgrade to a pay plan, or to update or just general advertising. I can no longer just open it and create a note. Because of this, I have now backed
9
Merry Christmas!
Sun, Dec 25th 2016 10:57p   Keith Strickland
Merry Christmas!! I hope everyone is having a great holiday. I thought now might be a good time to look back over the year and review some of the technology I’ve dealt with. Surface Pro 4: Last year I got a Surface Pro 4 tablet. I started the process of switching to it instead of my aging MacBook Pro. While I REALLY like the hardware and how everything works there were just a few issues which got on my nerves so bad I couldn’t ever completely make the switch. As far as performance, I
9
Visual Studio Code Editor
Fri, Nov 11th 2016 3:42p   Keith Strickland
I’ve been using the Visual Studio Code editor for the last couple of weeks and thought I would share my experience. I’ve mainly used this in a plain ‘ole polymer application which consists of html files. Using the editor this way has shown some of it’s shortcomings. Don’t get me wrong, I think it’s a fine editor and has a lot of features I really like. However with CSS, HTML and JavaScript all residing in the same file, a lot of the typeahead features just don
6
Work with Rich Text from DDS
Fri, Oct 21st 2016 5:33p   Keith Strickland
So you’ve created your shiny new web application using DDS and everything is really cool, except for the display of rich text. You’ve figured out that there is a multipart MIME object in the JSON delivered by DDS, and it has the HTML in that, but it still looks crappy. It has tags littered throughout and just doesn’t look good. Well, I think I’ve got the solution for you. If you look at that multipart MIME Object and find the ‘text/html’ entry you’ll not
8
A Polymer Avatar component
Wed, Oct 12th 2016 5:50p   Keith Strickland
At MWLUG a lot of people seemed surprised that I would create a component just for an avatar. My response to that is why wouldn’t you build an avatar component? So, in this post I’m going to show you how to build one for your applications. Let’s start out with the use case. We want to show an avatar for a person in various locations throughout our application. This avatar should do a few different things: Should show a picture of a person if one is attached to their person do
7
MWLUG Recap
Sat, Aug 20th 2016 11:27p   Keith Strickland
I had a great time at MWLUG this year. It was great to see so many familiar faces and friends, most of which I only see at the user groups. But, I just got home and thought I would share some of my thoughts and observations about the event and my take away about the state of our beloved Notes. As usual Richard Moy put together a great conference, so many thanks to him for making that event possible. The tone of the event was that most everyone is starting to realize that the Notes client is qui
11
Red Pill is the MWLUG Sponsor of the Week
Fri, Aug 12th 2016 12:21p   Keith Strickland
Red Pill Now is the Sponsor of the Week for MWLUG. Check out our video in the top right corner of the site. .huge-it-share-buttons { border:0px solid #0FB5D6; border-radius:5px; text-align:right; } #huge-it-share-buttons-top {margin-bottom:0px;} #huge-it-share-buttons-bottom {margin-top:0px;} .huge-it-share-buttons h3 { font-size:25px ; font-f
9
Domino svg support
Fri, Aug 12th 2016 12:09p   Keith Strickland
I’ve been messing with the Polymer vaadin-grid. If you enable hidable columns, a little graphic svg icon shows in the top right hand corner of the grid that produces a drop down menu of all the columns in the grid. You click one and it’ll hide that column. This was working great when running from my local gulp server. However when I put it on Domino, the little icon wasn’t showing, but the button was there (you couldn’t see it tho) and the menu worked when clicked. I di
8
Setting up a Polymer development environment
Fri, Jul 29th 2016 11:35a   Keith Strickland
When working with Polymer you’ll need a development environment. Google has created some great tools for doing this, mainly the Polymer-CLI. This is a command line interface for creating elements, applications, building applications (though I prefer a Gulp build system), a web server and some other misc. tools. This should be your starting point for setting up your development environment. To setup the polymer-cli you’ll need a few dependencies: Git Node.js (4.x) Bower Once those a
6
sortablejs – Drag-n-Drop without jQuery UI
Thu, Jul 21st 2016 4:59p   Keith Strickland
I had a need to enable drag-n-drop for a particular part of our portal. In the past I’ve always used jQuery-UI as it’s quite easy to enable drag-n-drop. Doing some research I came across a StackOverflow question about enabling drag-n-drop with Polymer. One of the answers mentioned sortablejs. This is a very minimalist library to enable drag-n-drop. Best part about this library is that it has a port for Polymer. BONUS! But there are several ports available: Angular, Knockout, Meteor,




Created and Maintained by Yancy Lent - About - Planet Lotus Blog - Advertising - Mobile Edition