Skip to content

Commit

Permalink
Trigger menu (#70)
Browse files Browse the repository at this point in the history
* Allow for manually triggering menu.

* More work on trigger-menu.

* more work on trigger menu.

* Get manual activation working.
  • Loading branch information
mrsweaters authored Aug 18, 2017
1 parent 1b7e45b commit 94a9d05
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 61 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,24 @@ Both the `selectTemplate` and the `menuItemTemplate` have access to the `item` o
}
```

### 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.

```
<a id="activateInput">@mention</a>
```

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

```js
activateLink.addEventListener('mousedown', function (e) {
e.preventDefault();
var input = document.getElementById('test');

tribute.showMenuForCollection(input);
});
```

## Events

### Replaced
Expand Down
97 changes: 83 additions & 14 deletions dist/tribute.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/tribute.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/tribute.min.js.map

Large diffs are not rendered by default.

41 changes: 13 additions & 28 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ <h1>Tribute Demo</h1>
<div class="large-8 columns">
<div class="callout large">
<h5>Tribute on <code>contenteditable</code> element:</h5>
<a id="activateInput">@mention</a>
<p id="test" class="tribute-demo-input" placeholder="Enter some text here"></p>

<h5>Tribute with a local collection (on <code>@</code>) and a remote one (on <code>#</code>):</h5>
Expand Down Expand Up @@ -125,11 +126,10 @@ <h5>Tribute on traditional form elements!</h5>
},

// function retrieving an array of objects
values: function (text, cb) {
remoteSearch(text, function (users) {
cb(users);
});
},
values: [
{name: 'Bob Bill', email: 'bobbill@example.com'},
{name: 'Steve Stevenston', email: 'steve@example.com'}
],

lookup: 'name',

Expand All @@ -149,30 +149,15 @@ <h5>Tribute on traditional form elements!</h5>
tribute.appendCurrent(values);
});

var activateLink = document.getElementById('activateInput');

function remoteSearch(text, cb) {
var URL = 'http://mockbin.org/bin/7de4a9db-5599-4634-be1c-03b2489226e6';
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function ()
{
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
// Filter data now, as that isn't implemented on mockbin.org yet.
// Mock data: http://mockbin.org/bin/7de4a9db-5599-4634-be1c-03b2489226e6
if (text.length) {
data = data.filter(function (item) {
return item.name.toLowerCase().indexOf(text.toLowerCase()) !== -1;
});
}
cb(data);
} else if (xhr.status === 403) {
cb([]);
}
}
};
xhr.open("GET", URL + '?q=' + text, true);
xhr.send();
if (activateLink) {
activateLink.addEventListener('mousedown', function (e) {
e.preventDefault();
var input = document.getElementById('test');

tribute.showMenuForCollection(input);
});
}
</script>
</body>
Expand Down
62 changes: 62 additions & 0 deletions src/Tribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class Tribute {
if (!this.isActive) {
return
}

let items = this.search.filter(this.current.mentionText, values, {
pre: '<span>',
post: '</span>',
Expand All @@ -217,6 +218,7 @@ class Tribute {

this.current.filteredItems = items


let ul = this.menu.querySelector('ul')

if (!items.length) {
Expand Down Expand Up @@ -258,6 +260,66 @@ class Tribute {
}
}

showMenuForCollection(element, collectionIndex) {
if (element !== document.activeElement) {
this.placeCaretAtEnd(element)
}

this.current.collection = this.collection[collectionIndex || 0]
this.current.element = element

this.insertTextAtCursor(this.current.collection.trigger)
this.showMenuFor(element)
}

// TODO: make sure this works for inputs/textareas
placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}

// for contenteditable
insertTextAtCursor(text) {
var sel, range, html;
sel = window.getSelection();
range = sel.getRangeAt(0);
range.deleteContents();
var textNode = document.createTextNode(text);
range.insertNode(textNode);
range.selectNodeContents(textNode)
range.collapse(false)
sel.removeAllRanges()
sel.addRange(range)
}

// for regular inputs
insertAtCaret(textarea, text) {
var scrollPos = txtarea.scrollTop;
var caretPos = txtarea.selectionStart;

var front = (txtarea.value).substring(0, caretPos);
var back = (txtarea.value).substring(txtarea.selectionEnd, txtarea.value.length);
txtarea.value = front + text + back;
caretPos = caretPos + text.length;
txtarea.selectionStart = caretPos;
txtarea.selectionEnd = caretPos;
txtarea.focus();
txtarea.scrollTop = scrollPos;
}

hideMenu() {
if (this.menu) {
this.menu.style.cssText = 'display: none;'
Expand Down
3 changes: 0 additions & 3 deletions src/TributeEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class TributeEvents {

click(instance, event) {
let tribute = instance.tribute

if (tribute.menu && tribute.menu.contains(event.target)) {
let li = event.target
event.preventDefault()
Expand All @@ -71,8 +70,6 @@ class TributeEvents {
}
tribute.selectItemAtIndex(li.getAttribute('data-index'), event)
tribute.hideMenu()
} else if (tribute.current.element) {
tribute.hideMenu()
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/TributeMenuEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class TributeMenuEvents {
bind(menu) {
menu.addEventListener('keydown',
this.tribute.events.keydown.bind(this.menu, this), false)
this.tribute.range.getDocument().addEventListener('click',
this.tribute.range.getDocument().addEventListener('mousedown',
this.tribute.events.click.bind(null, this), false)
window.addEventListener('resize', this.debounce(() => {
if (this.tribute.isActive) {
Expand Down
26 changes: 13 additions & 13 deletions src/TributeRange.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ class TributeRange {
positionMenuAtCaret(scrollTo) {
let context = this.tribute.current,
coordinates

let info = this.getTriggerInfo(false, false, true, this.tribute.allowSpaces)

if (info !== undefined) {
if (typeof info !== 'undefined') {
if (!this.isContentEditable(context.element)) {
coordinates = this.getTextAreaOrInputUnderlinePosition(this.getDocument().activeElement,
info.mentionPosition)
Expand All @@ -32,14 +33,13 @@ class TributeRange {
coordinates = this.getContentEditableCaretPosition(info.mentionPosition)
}

// Move the button into place.
this.tribute.menu.style.cssText = `top: ${coordinates.top}px;
left: ${coordinates.left}px;
position: absolute;
zIndex: 10000;
display: block;`

setTimeout(() => {
this.tribute.menu.style.cssText = `top: ${coordinates.top}px;
left: ${coordinates.left}px;
position: absolute;
zIndex: 10000;
display: block;`

if (scrollTo) this.scrollIntoView(this.getDocument().activeElement)
}, 0)
} else {
Expand Down Expand Up @@ -81,6 +81,7 @@ class TributeRange {
targetElement.focus()
}

// TODO: this may not be necessary anymore as we are using mouseup instead of click
resetSelection(targetElement, path, offset) {
if (!this.isContentEditable(targetElement)) {
if (targetElement !== this.getDocument().activeElement) {
Expand All @@ -93,7 +94,8 @@ class TributeRange {

replaceTriggerText(text, requireLeadingSpace, hasTrailingSpace, originalEvent) {
let context = this.tribute.current
this.resetSelection(context.element, context.selectedPath, context.selectedOffset)
// TODO: this may not be necessary anymore as we are using mouseup instead of click
// this.resetSelection(context.element, context.selectedPath, context.selectedOffset)

let info = this.getTriggerInfo(true, hasTrailingSpace, requireLeadingSpace, this.tribute.allowSpaces)

Expand Down Expand Up @@ -181,8 +183,7 @@ class TributeRange {
}
}

getContentEditableSelectedPath() {
// content editable
getContentEditableSelectedPath(ctx) {
let sel = this.getWindowSelection()
let selected = sel.anchorNode
let path = []
Expand Down Expand Up @@ -244,8 +245,7 @@ class TributeRange {
if (!this.isContentEditable(ctx.element)) {
selected = this.getDocument().activeElement
} else {
// content editable
let selectionInfo = this.getContentEditableSelectedPath()
let selectionInfo = this.getContentEditableSelectedPath(ctx)

if (selectionInfo) {
selected = selectionInfo.selected
Expand Down

0 comments on commit 94a9d05

Please sign in to comment.