Tribute

ES6 Native @mentions

README

Tribute

CDNJS version Build Status

A cross-browser @mention engine written in ES6, no dependencies. Tested in Firefox, Chrome, iOS Safari, Safari, IE 9+, Edge 12+, Android 4+, and Windows Phone.


Installing


There are a few ways to install Tribute; Bower, as an NPM Module, or by downloading from thedist folder in this repo.

NPM Module


You can install Tribute by running:

  1. ``` sh
  2. npm install tributejs
  3. ```

Or by adding Tribute to your package.json file.

Import into your ES6 code.

  1. ``` js
  2. import Tribute from "tributejs";
  3. ```

Ruby Gem


To use Tribute within a Rails project, you can add the following to the app's Gemfile:

    gem 'tribute'

Then, add the following to app/assets/javascripts/application.js:

  1. ``` js
  2. *= require tribute
  3. ```

And in app/assets/stylesheets/application.css:

  1. ```css
  2. //= require tribute
  3. ```

Webpack


To add Tribute to your webpack build process, start by adding it to your package.json and running npm install.

After installing, you need to update your Babel module loader to not exclude Tribute from being compiled by Webpack:

  1. ``` js
  2. {
  3.     test: /\.js$/,
  4.     loader: 'babel',
  5.     exclude: /node_modules\/(?!tributejs)/
  6. }
  7. ```

Download or Clone


Or you can download the repo or clone it localy with this command:

  1. ``` sh
  2. git clone git@github.com:zurb/tribute.git
  3. ```

You can then copy the files in the dist directory to your project.

  1. ``` html
  2. <link rel="stylesheet" href="js/tribute.css" />
  3. <script src="js/tribute.js"></script>
  4. ```

That's it! Now you are ready to initialize Tribute.

Initializing


There are two ways to initialize Tribute, by passing an array of "collections" or by passing one collection object.

  1. ``` js
  2. var tribute = new Tribute({
  3.   values: [
  4.     { key: "Phil Heartman", value: "pheartman" },
  5.     { key: "Gordon Ramsey", value: "gramsey" }
  6.   ]
  7. });
  8. ```

You can pass multiple collections on initialization by passing in an array of collection objects to collection.

  1. ``` js
  2. var tribute = new Tribute({
  3.   collection: []
  4. });
  5. ```

Attaching to elements


Once initialized, Tribute can be attached to an input, textarea, or an element that supports contenteditable.

  1. ``` html
  2. <div id="caaanDo">I'm Mr. Meeseeks, look at me!</div>
  3. <div class="mentionable">Some text here.</div>
  4. <div class="mentionable">Some more text over here.</div>
  5. <script>
  6.   tribute.attach(document.getElementById("caaanDo"));
  7.   // also works with NodeList
  8.   tribute.attach(document.querySelectorAll(".mentionable"));
  9. </script>
  10. ```

A Collection


Collections are configuration objects for Tribute, you can have multiple for each instance. This is useful for scenarios where you may want to match multiple trigger keys, such as @ for users and # for projects.

Collection object shown with defaults:

  1. ``` js
  2. {
  3.   // symbol or string that starts the lookup
  4.   trigger: '@',

  5.   // element to target for @mentions
  6.   iframe: null,

  7.   // class added in the flyout menu for active item
  8.   selectClass: 'highlight',

  9.   // class added to the menu container
  10.   containerClass: 'tribute-container',

  11.   // class added to each list item
  12.   itemClass: '',

  13.   // function called on select that returns the content to insert
  14.   selectTemplate: function (item) {
  15.     return '@' + item.original.value;
  16.   },

  17.   // template for displaying item in menu
  18.   menuItemTemplate: function (item) {
  19.     return item.string;
  20.   },

  21.   // template for when no match is found (optional),
  22.   // If no template is provided, menu is hidden.
  23.   noMatchTemplate: null,

  24.   // specify an alternative parent container for the menu
  25.   // container must be a positioned element for the menu to appear correctly ie. `position: relative;`
  26.   // default container is the body
  27.   menuContainer: document.body,

  28.   // column to search against in the object (accepts function or string)
  29.   lookup: 'key',

  30.   // column that contains the content to insert by default
  31.   fillAttr: 'value',

  32.   // REQUIRED: array of objects to match or a function that returns data (see 'Loading remote data' for an example)
  33.   values: [],

  34.   // When your values function is async, an optional loading template to show
  35.   loadingItemTemplate: null,

  36.   // specify whether a space is required before the trigger string
  37.   requireLeadingSpace: true,

  38.   // specify whether a space is allowed in the middle of mentions
  39.   allowSpaces: false,

  40.   // optionally specify a custom suffix for the replace text
  41.   // (defaults to empty space if undefined)
  42.   replaceTextSuffix: '\n',

  43.   // specify whether the menu should be positioned.  Set to false and use in conjuction with menuContainer to create an inline menu
  44.   // (defaults to true)
  45.   positionMenu: true,

  46.   // when the spacebar is hit, select the current match
  47.   spaceSelectsMatch: false,

  48.   // turn tribute into an autocomplete
  49.   autocompleteMode: false,

  50.   // Customize the elements used to wrap matched strings within the results list
  51.   // defaults to if undefined
  52.   searchOpts: {
  53.     pre: '<span>',
  54.     post: '</span>',
  55.     skip: false // true will skip local search, useful if doing server-side search
  56.   },

  57.   // Limits the number of items in the menu
  58.   menuItemLimit: 25,

  59.   // specify the minimum number of characters that must be typed before menu appears
  60.   menuShowMinLength: 0
  61. }
  62. ```

Dynamic lookup column


The lookup column can also be passed a function to construct a string to query against. This is useful if your payload has multiple attributes that you would like to query against but you can't modify the payload returned from the server to include a concatenated lookup column.

  1. ``` js
  2. {
  3.   lookup: function (person, mentionText) {
  4.     return person.name + person.email;
  5.   }
  6. }
  7. ```

Template Item


Both the selectTemplate and the menuItemTemplate have access to the item object. This is a meta object containing the matched object from your values collection, wrapped in a search result.

  1. ``` js
  2. {
  3.   index: 0;
  4.   original: {
  5.   } // your original object from values array
  6.   score: 5;
  7.   string: "<span>J</span><span>o</span>rdan Hum<span>p</span>hreys";
  8. }
  9. ```

Trigger tribute programmatically


Tribute can be manually triggered by calling an instances showMenuForCollection method. This is great for trigging tribute on an input by clicking an anchor or button element.

  1. ```
  2. <a id="activateInput">@mention</a>
  3. ```

Then you can bind a mousedown event to the anchor and call showMenuForCollection.

  1. ``` js
  2. activateLink.addEventListener("mousedown", function(e) {
  3.   e.preventDefault();
  4.   var input = document.getElementById("test");

  5.   tribute.showMenuForCollection(input);
  6. });
  7. ```

Note that showMenuForCollection has an optional second parameter called collectionIndex that defaults to 0. This allows you to specify which collection you want to trigger with the first index starting at 0.

For example, if you want to trigger the second collection you would use the following snippet: tribute.showMenuForCollection(input, 1);

Events


Replaced


You can bind to the tribute-replaced event to know when we have updated your targeted Tribute element.

If your element has an ID of myElement:

  1. ``` js
  2. document
  3.   .getElementById("myElement")
  4.   .addEventListener("tribute-replaced", function(e) {
  5.     console.log(
  6.       "Original event that triggered text replacement:",
  7.       e.detail.event
  8.     );
  9.     console.log("Matched item:", e.detail.item);
  10.   });
  11. ```

No Match


You can bind to the tribute-no-match event to know when no match is found in your collection.

If your element has an ID of myElement:

  1. ``` js
  2. document
  3.   .getElementById("myElement")
  4.   .addEventListener("tribute-no-match", function(e) {
  5.     console.log("No match found!");
  6.   });
  7. ```

Active State Detection


You can bind to the tribute-active-true or tribute-active-false events to detect when the menu is open or closed respectively.

  1. ``` js
  2. document
  3.   .getElementById("myElement")
  4.   .addEventListener("tribute-active-true", function(e) {
  5.     console.log("Menu opened!");
  6.   });
  7. ```

  1. ``` js
  2. document
  3.   .getElementById("myElement")
  4.   .addEventListener("tribute-active-false", function(e) {
  5.     console.log("Menu closed!");
  6.   });
  7. ```

Tips


Some useful approaches to common roadblocks when implementing @mentions.

Updating a collection with new data


You can update an instance of Tribute on the fly. If you have new data you want to insert into the current active collection you can access the collection values array directly:

  1. ``` js
  2. tribute.appendCurrent([
  3.   { name: "Howard Johnson", occupation: "Panda Wrangler", age: 27 },
  4.   { name: "Fluffy Croutons", occupation: "Crouton Fluffer", age: 32 }
  5. ]);
  6. ```

This would update the first configuration object in the collection array with new values. You can access and update any attribute on the collection in this way.

You can also append new values to an arbitrary collection by passing an index to append.

  1. ``` js
  2. tribute.append(2, [
  3.   { name: "Howard Johnson", occupation: "Panda Wrangler", age: 27 },
  4.   { name: "Fluffy Croutons", occupation: "Crouton Fluffer", age: 32 }
  5. ]);
  6. ```

This will append the new values to the third collection.

Programmatically detecting an active Tribute dropdown


If you need to know when Tribute is active you can access the isActive property of an instance.

  1. ``` js
  2. if (tribute.isActive) {
  3.   console.log("Somebody is being mentioned!");
  4. } else {
  5.   console.log("Who's this guy talking to?");
  6. }
  7. ```

Links inside contenteditable are not clickable.


If you want to embed a link in your selectTemplate then you need to make sure that the
anchor is wrapped in an element with contenteditable="false". This makes the anchor
clickable _and_ fixes issues with matches being modifiable.

  1. ``` js
  2. var tribute = new Tribute({
  3.   values: [
  4.     {
  5.       key: "Jordan Humphreys",
  6.       value: "Jordan Humphreys",
  7.       email: "getstarted@zurb.com"
  8.     },
  9.     {
  10.       key: "Sir Walter Riley",
  11.       value: "Sir Walter Riley",
  12.       email: "getstarted+riley@zurb.com"
  13.     }
  14.   ],
  15.   selectTemplate: function(item) {
  16.     return (
  17.       '+
  18.       item.original.email +
  19.       '">' +
  20.       item.original.value +
  21.       "</a></span>"
  22.     );
  23.   }
  24. });
  25. ```

























AngularJS 1.5+ — angular-tribute byZURB

Angular 2+ - ngx-tribute byLadder.io

Ruby — tribute-rb byZURB

Ember – ember-tribute byMalayaliRobz

WYSIWYG Editor Support


- Froala Editor - https://www.froala.com/wysiwyg-editor/examples/tribute-js

Brought to you by


ZURB, the creators of Helio

Design successful products by rapidly revealing key user behaviors. Helio makes it easy to get reactions on your designs quickly so your team can focus on solving the right problems, right now.