{"id":13472482,"url":"https://github.com/zurb/tribute","last_synced_at":"2025-05-13T16:07:26.472Z","repository":{"id":5438605,"uuid":"49915944","full_name":"zurb/tribute","owner":"zurb","description":"ES6 Native @mentions","archived":false,"fork":false,"pushed_at":"2024-07-13T10:37:22.000Z","size":5138,"stargazers_count":2036,"open_issues_count":213,"forks_count":305,"subscribers_count":38,"default_branch":"master","last_synced_at":"2024-10-29T11:05:10.612Z","etag":null,"topics":["autocomplete","mentions"],"latest_commit_sha":null,"homepage":"https://zurb.github.io/tribute/example/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zurb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-01-19T00:54:39.000Z","updated_at":"2024-10-22T21:18:21.000Z","dependencies_parsed_at":"2023-02-15T10:15:35.754Z","dependency_job_id":"32c308fa-0e67-4832-b033-9ce799ba5449","html_url":"https://github.com/zurb/tribute","commit_stats":{"total_commits":420,"total_committers":66,"mean_commits":6.363636363636363,"dds":0.3547619047619047,"last_synced_commit":"252ec344a75b624268aef1cdbf80afc30b3cbe2b"},"previous_names":[],"tags_count":62,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zurb%2Ftribute","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zurb%2Ftribute/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zurb%2Ftribute/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zurb%2Ftribute/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zurb","download_url":"https://codeload.github.com/zurb/tribute/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250513383,"owners_count":21443201,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autocomplete","mentions"],"created_at":"2024-07-31T16:00:55.016Z","updated_at":"2025-04-23T20:46:18.510Z","avatar_url":"https://github.com/zurb.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","编辑器相关"],"sub_categories":[],"readme":"# Tribute\n\n[![CDNJS version](https://img.shields.io/cdnjs/v/tributejs.svg)](https://cdnjs.com/libraries/tributejs) [![Build Status](https://travis-ci.org/zurb/tribute.svg?branch=master)](https://travis-ci.org/zurb/tribute)\n\nA 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.\n\n- [Installing](#installing)\n- [Initializing](#initializing)\n- [A Collection](#a-collection)\n- [Events](#events)\n- [Tips](#tips)\n- [Framework Support](#framework-support)\n- [WYSIWYG Editor Support](#wysiwyg-editor-support)\n- [Example](https://zurb.github.io/tribute/example/)\n\n## Installing\n\nThere are a few ways to install Tribute; [Bower](http://bower.io/), as an [NPM Module](https://npmjs.com/package/tributejs), or by [downloading](https://github.com/zurb/tribute/archive/master.zip) from the `dist` folder in this repo.\n\n### NPM Module\n\nYou can install Tribute by running:\n\n```shell\nnpm install tributejs\n```\n\nOr by adding Tribute to your `package.json` file.\n\nImport into your ES6 code.\n\n```js\nimport Tribute from \"tributejs\";\n```\n\n### Ruby Gem\n\nTo use Tribute within a Rails project, you can add the following to the app's Gemfile:\n\n    gem 'tribute'\n\nThen, add the following to `app/assets/javascripts/application.js`:\n\n```js\n*= require tribute\n```\n\nAnd in `app/assets/stylesheets/application.css`:\n\n```css\n//= require tribute\n```\n\n### Webpack\n\nTo add Tribute to your webpack build process, start by adding it to your package.json and running `npm install`.\n\nAfter installing, you need to update your Babel module loader to not exclude Tribute from being compiled by Webpack:\n\n```js\n{\n    test: /\\.js$/,\n    loader: 'babel',\n    exclude: /node_modules\\/(?!tributejs)/\n}\n```\n\n### Download or Clone\n\nOr you can [download the repo](https://github.com/zurb/tribute/archive/master.zip) or clone it localy with this command:\n\n```shell\ngit clone git@github.com:zurb/tribute.git\n```\n\nYou can then copy the files in the `dist` directory to your project.\n\n```html\n\u003clink rel=\"stylesheet\" href=\"js/tribute.css\" /\u003e\n\u003cscript src=\"js/tribute.js\"\u003e\u003c/script\u003e\n```\n\nThat's it! Now you are ready to initialize Tribute.\n\n## Initializing\n\nThere are two ways to initialize Tribute, by passing an array of \"collections\" or by passing one collection object.\n\n```js\nvar tribute = new Tribute({\n  values: [\n    { key: \"Phil Heartman\", value: \"pheartman\" },\n    { key: \"Gordon Ramsey\", value: \"gramsey\" }\n  ]\n});\n```\n\nYou can pass multiple collections on initialization by passing in an array of collection objects to `collection`.\n\n```js\nvar tribute = new Tribute({\n  collection: []\n});\n```\n\n### Attaching to elements\n\nOnce initialized, Tribute can be attached to an `input`, `textarea`, or an element that supports `contenteditable`.\n\n```html\n\u003cdiv id=\"caaanDo\"\u003eI'm Mr. Meeseeks, look at me!\u003c/div\u003e\n\n\u003cdiv class=\"mentionable\"\u003eSome text here.\u003c/div\u003e\n\u003cdiv class=\"mentionable\"\u003eSome more text over here.\u003c/div\u003e\n\n\u003cscript\u003e\n  tribute.attach(document.getElementById(\"caaanDo\"));\n\n  // also works with NodeList\n  tribute.attach(document.querySelectorAll(\".mentionable\"));\n\u003c/script\u003e\n```\n\n## A Collection\n\nCollections 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.\n\nCollection object shown with defaults:\n\n```js\n{\n  // symbol or string that starts the lookup\n  trigger: '@',\n\n  // element to target for @mentions\n  iframe: null,\n\n  // class added in the flyout menu for active item\n  selectClass: 'highlight',\n\n  // class added to the menu container\n  containerClass: 'tribute-container',\n\n  // class added to each list item\n  itemClass: '',\n\n  // function called on select that returns the content to insert\n  selectTemplate: function (item) {\n    return '@' + item.original.value;\n  },\n\n  // template for displaying item in menu\n  menuItemTemplate: function (item) {\n    return item.string;\n  },\n\n  // template for when no match is found (optional),\n  // If no template is provided, menu is hidden.\n  noMatchTemplate: null,\n\n  // specify an alternative parent container for the menu\n  // container must be a positioned element for the menu to appear correctly ie. `position: relative;`\n  // default container is the body\n  menuContainer: document.body,\n\n  // column to search against in the object (accepts function or string)\n  lookup: 'key',\n\n  // column that contains the content to insert by default\n  fillAttr: 'value',\n\n  // REQUIRED: array of objects to match or a function that returns data (see 'Loading remote data' for an example)\n  values: [],\n\n  // When your values function is async, an optional loading template to show\n  loadingItemTemplate: null,\n\n  // specify whether a space is required before the trigger string\n  requireLeadingSpace: true,\n\n  // specify whether a space is allowed in the middle of mentions\n  allowSpaces: false,\n\n  // optionally specify a custom suffix for the replace text\n  // (defaults to empty space if undefined)\n  replaceTextSuffix: '\\n',\n\n  // specify whether the menu should be positioned.  Set to false and use in conjuction with menuContainer to create an inline menu\n  // (defaults to true)\n  positionMenu: true,\n\n  // when the spacebar is hit, select the current match\n  spaceSelectsMatch: false,\n\n  // turn tribute into an autocomplete\n  autocompleteMode: false,\n\n  // Customize the elements used to wrap matched strings within the results list\n  // defaults to \u003cspan\u003e\u003c/span\u003e if undefined\n  searchOpts: {\n    pre: '\u003cspan\u003e',\n    post: '\u003c/span\u003e',\n    skip: false // true will skip local search, useful if doing server-side search\n  },\n\n  // Limits the number of items in the menu\n  menuItemLimit: 25,\n\n  // specify the minimum number of characters that must be typed before menu appears\n  menuShowMinLength: 0\n}\n```\n\n### Dynamic lookup column\n\nThe `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.\n\n```js\n{\n  lookup: function (person, mentionText) {\n    return person.name + person.email;\n  }\n}\n```\n\n### Template Item\n\nBoth 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.\n\n```js\n{\n  index: 0;\n  original: {\n  } // your original object from values array\n  score: 5;\n  string: \"\u003cspan\u003eJ\u003c/span\u003e\u003cspan\u003eo\u003c/span\u003erdan Hum\u003cspan\u003ep\u003c/span\u003ehreys\";\n}\n```\n\n### Trigger tribute programmatically\n\nTribute 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.\n\n```\n\u003ca id=\"activateInput\"\u003e@mention\u003c/a\u003e\n```\n\nThen you can bind a `mousedown` event to the anchor and call `showMenuForCollection`.\n\n```js\nactivateLink.addEventListener(\"mousedown\", function(e) {\n  e.preventDefault();\n  var input = document.getElementById(\"test\");\n\n  tribute.showMenuForCollection(input);\n});\n```\n\nNote 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.\n\nFor example, if you want to trigger the second collection you would use the following snippet: `tribute.showMenuForCollection(input, 1);`\n\n## Events\n\n### Replaced\n\nYou can bind to the `tribute-replaced` event to know when we have updated your targeted Tribute element.\n\nIf your element has an ID of `myElement`:\n\n```js\ndocument\n  .getElementById(\"myElement\")\n  .addEventListener(\"tribute-replaced\", function(e) {\n    console.log(\n      \"Original event that triggered text replacement:\",\n      e.detail.event\n    );\n    console.log(\"Matched item:\", e.detail.item);\n  });\n```\n\n### No Match\n\nYou can bind to the `tribute-no-match` event to know when no match is found in your collection.\n\nIf your element has an ID of `myElement`:\n\n```js\ndocument\n  .getElementById(\"myElement\")\n  .addEventListener(\"tribute-no-match\", function(e) {\n    console.log(\"No match found!\");\n  });\n```\n\n### Active State Detection\n\nYou can bind to the `tribute-active-true` or `tribute-active-false` events to detect when the menu is open or closed respectively.\n\n```js\ndocument\n  .getElementById(\"myElement\")\n  .addEventListener(\"tribute-active-true\", function(e) {\n    console.log(\"Menu opened!\");\n  });\n```\n\n```js\ndocument\n  .getElementById(\"myElement\")\n  .addEventListener(\"tribute-active-false\", function(e) {\n    console.log(\"Menu closed!\");\n  });\n```\n\n## Tips\n\nSome useful approaches to common roadblocks when implementing @mentions.\n\n### Updating a collection with new data\n\nYou 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:\n\n```js\ntribute.appendCurrent([\n  { name: \"Howard Johnson\", occupation: \"Panda Wrangler\", age: 27 },\n  { name: \"Fluffy Croutons\", occupation: \"Crouton Fluffer\", age: 32 }\n]);\n```\n\nThis 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.\n\nYou can also append new values to an arbitrary collection by passing an index to `append`.\n\n```js\ntribute.append(2, [\n  { name: \"Howard Johnson\", occupation: \"Panda Wrangler\", age: 27 },\n  { name: \"Fluffy Croutons\", occupation: \"Crouton Fluffer\", age: 32 }\n]);\n```\n\nThis will append the new values to the third collection.\n\n### Programmatically detecting an active Tribute dropdown\n\nIf you need to know when Tribute is active you can access the `isActive` property of an instance.\n\n```js\nif (tribute.isActive) {\n  console.log(\"Somebody is being mentioned!\");\n} else {\n  console.log(\"Who's this guy talking to?\");\n}\n```\n\n### Links inside contenteditable are not clickable.\n\nIf you want to embed a link in your `selectTemplate` then you need to make sure that the\nanchor is wrapped in an element with `contenteditable=\"false\"`. This makes the anchor\nclickable _and_ fixes issues with matches being modifiable.\n\n```js\nvar tribute = new Tribute({\n  values: [\n    {\n      key: \"Jordan Humphreys\",\n      value: \"Jordan Humphreys\",\n      email: \"getstarted@zurb.com\"\n    },\n    {\n      key: \"Sir Walter Riley\",\n      value: \"Sir Walter Riley\",\n      email: \"getstarted+riley@zurb.com\"\n    }\n  ],\n  selectTemplate: function(item) {\n    return (\n      '\u003cspan contenteditable=\"false\"\u003e\u003ca href=\"http://zurb.com\" target=\"_blank\" title=\"' +\n      item.original.email +\n      '\"\u003e' +\n      item.original.value +\n      \"\u003c/a\u003e\u003c/span\u003e\"\n    );\n  }\n});\n```\n\n### How do I add an image to the items in the list?\n\nYou can override the default `menuItemTemplate` with your own output on initialization. This allows you to replace the `innerHTML` of the `li` of each item in the list. You can use `item.string` to return the markup for the fuzzy match.\n\n```js\n{\n  //..other config options\n  menuItemTemplate: function (item) {\n    return '\u003cimg src=\"'+item.original.avatar_url + '\"\u003e' + item.string;\n  }\n}\n```\n\n### Embedding Tribute in a scrollable container.\n\nSometimes you may need to have the Tribute menu attach to a scrollable parent element so that if the user scrolls the container the menu will scroll with it. To do this, you can set `menuContainer` to the node that is the scrollable parent.\n\n```js\n{\n  //..other config options\n  menuContainer: document.getElementById(\"wrapper\");\n}\n```\n\n### Loading remote data\n\nIf your data set is large or would like to pre filter your data you can load dynamically by setting the `values` to a function.\n\n```js\n{\n  //..other config options\n  // function retrieving an array of objects\n  values: function (text, cb) {\n    remoteSearch(text, users =\u003e cb(users));\n  },\n  lookup: 'name',\n  fillAttr: 'name'\n}\n```\n\nYou would then define a function, in this case `remoteSearch`, that returns your data from the backend.\n\n```js\nfunction remoteSearch(text, cb) {\n  var URL = \"YOUR DATA ENDPOINT\";\n  xhr = new XMLHttpRequest();\n  xhr.onreadystatechange = function() {\n    if (xhr.readyState === 4) {\n      if (xhr.status === 200) {\n        var data = JSON.parse(xhr.responseText);\n        cb(data);\n      } else if (xhr.status === 403) {\n        cb([]);\n      }\n    }\n  };\n  xhr.open(\"GET\", URL + \"?q=\" + text, true);\n  xhr.send();\n}\n```\n\n### Hide menu when no match is returned\n\nIf you want the menu to not show when no match is found, you can set your `noMatchTemplate` config to the following:\n\n```js\nnoMatchTemplate: function () {\n  return '\u003cspan style:\"visibility: hidden;\"\u003e\u003c/span\u003e';\n}\n```\n\n### Detaching Tribute instances\n\nWhen you want to remove Tribute from an element you can call `detach`.\n\n```js\ntribute.detach(document.getElementById(\"caaanDo\"));\n```\n\nThis will remove all event listeners from the DOM that are associated with that element.\n\n### Trigger on multiple character strings\n\nIt is also possible to configure Tribute to trigger on a string consisting of multiple characters.\n\nThis example shows the usage of Tribute for autocompletion of variables:\n\n```js\nvar tribute = new Tribute({\n  trigger: \"{{\",\n  values: [\n    { key: \"red\", value: \"#FF0000\" },\n    { key: \"green\", value: \"#00FF00\" }\n  ],\n  selectTemplate: function(item) {\n    return \"{{\" + item.original.key + \"}}\";\n  },\n  menuItemTemplate: function(item) {\n    return item.original.key + \" = \" + item.original.value;\n  }\n});\n```\n\n## Framework Support\n\nVue.js — [vue-tribute](https://github.com/syropian/vue-tribute) by **@syropian**\n\nAngularJS 1.5+ — [angular-tribute](https://github.com/zurb/angular-tribute) by **ZURB**\n\nAngular 2+ - [ngx-tribute](https://github.com/ladderio/ngx-tribute) by **Ladder.io**\n\nRuby — [tribute-rb](https://github.com/zurb/tribute-rb) by **ZURB**\n\nEmber – [ember-tribute](https://github.com/MalayaliRobz/ember-tribute) by **MalayaliRobz**\n\n## WYSIWYG Editor Support\n\n- Froala Editor - https://www.froala.com/wysiwyg-editor/examples/tribute-js\n\n## Brought to you by\n\n[ZURB](https://zurb.com), the creators of [Helio](https://helio.app)\n\nDesign successful products by rapidly revealing key user behaviors. [Helio](https://helio.app) makes it easy to get reactions on your designs quickly so your team can focus on solving the right problems, right now.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzurb%2Ftribute","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzurb%2Ftribute","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzurb%2Ftribute/lists"}