|
1
|
(function(F/*fossil object*/){ |
|
2
|
/** |
|
3
|
A basic API for creating and managing a copy-to-clipboard button. |
|
4
|
|
|
5
|
Requires: fossil.bootstrap, fossil.dom |
|
6
|
*/ |
|
7
|
const D = F.dom; |
|
8
|
|
|
9
|
/** |
|
10
|
Initializes element e as a copy button using the given options |
|
11
|
object. |
|
12
|
|
|
13
|
The first argument may be a DOM element or a string (CSS selector |
|
14
|
suitable for use with document.querySelector()). |
|
15
|
|
|
16
|
Options: |
|
17
|
|
|
18
|
.copyFromElement: DOM element |
|
19
|
|
|
20
|
.copyFromId: DOM element ID |
|
21
|
|
|
22
|
.extractText: optional callback which is triggered when the copy |
|
23
|
button is clicked. It must return the text to copy to the |
|
24
|
clipboard. The default is to extract it from the copy-from |
|
25
|
element, using its [value] member, if it has one, else its |
|
26
|
[innerText]. A client-provided callback may use any data source |
|
27
|
it likes, so long as it's synchronous. If this function returns a |
|
28
|
falsy value then the clipboard is not modified. This function is |
|
29
|
called with the fully expanded/resolved options object as its |
|
30
|
"this" (that's a different instance than the one passed to this |
|
31
|
function!). |
|
32
|
|
|
33
|
At least one of copyFromElement, copyFromId, or extractText must |
|
34
|
be provided, but if copyFromId is not set and e.dataset.copyFromId |
|
35
|
is then that value is used in its place. extractText() trumps the |
|
36
|
other two options. |
|
37
|
|
|
38
|
.cssClass: optional CSS class, or list of classes, to apply to e. |
|
39
|
|
|
40
|
.style: optional object of properties to copy directly into |
|
41
|
e.style. |
|
42
|
|
|
43
|
.oncopy: an optional callback function which is added as an event |
|
44
|
listener for the 'text-copied' event (see below). There is |
|
45
|
functionally no difference from setting this option or adding a |
|
46
|
'text-copied' event listener to the element, and this option is |
|
47
|
considered to be a convenience form of that. |
|
48
|
|
|
49
|
Note that this function's own defaultOptions object holds default |
|
50
|
values for some options. Any changes made to that object affect |
|
51
|
any future calls to this function. |
|
52
|
|
|
53
|
Be aware that clipboard functionality might or might not be |
|
54
|
available in any given environment. If this button appears to |
|
55
|
have no effect, that may be because it is not enabled/available |
|
56
|
in the current platform. |
|
57
|
|
|
58
|
The copy button emits custom event 'text-copied' after it has |
|
59
|
successfully copied text to the clipboard. The event's "detail" |
|
60
|
member is an object with a "text" property holding the copied |
|
61
|
text. Other properties may be added in the future. The event is |
|
62
|
not fired if copying to the clipboard fails (e.g. is not |
|
63
|
available in the current environment). |
|
64
|
|
|
65
|
The copy button's click handler is suppressed (becomes a no-op) |
|
66
|
for as long as the element has the "disabled" attribute. |
|
67
|
|
|
68
|
Returns the copy-initialized element. |
|
69
|
|
|
70
|
Example: |
|
71
|
|
|
72
|
const button = fossil.copyButton('#my-copy-button', { |
|
73
|
copyFromId: 'some-other-element-id' |
|
74
|
}); |
|
75
|
button.addEventListener('text-copied',function(ev){ |
|
76
|
console.debug("Copied text:",ev.detail.text); |
|
77
|
}); |
|
78
|
*/ |
|
79
|
F.copyButton = function f(e, opt){ |
|
80
|
if('string'===typeof e){ |
|
81
|
e = document.querySelector(e); |
|
82
|
} |
|
83
|
opt = F.mergeLastWins(f.defaultOptions, opt); |
|
84
|
if(opt.cssClass){ |
|
85
|
D.addClass(e, opt.cssClass); |
|
86
|
} |
|
87
|
var srcId, srcElem; |
|
88
|
if(opt.copyFromElement){ |
|
89
|
srcElem = opt.copyFromElement; |
|
90
|
}else if((srcId = opt.copyFromId || e.dataset.copyFromId)){ |
|
91
|
srcElem = document.querySelector('#'+srcId); |
|
92
|
} |
|
93
|
const extract = opt.extractText || ( |
|
94
|
undefined===srcElem.value ? ()=>srcElem.innerText : ()=>srcElem.value |
|
95
|
); |
|
96
|
D.copyStyle(e, opt.style); |
|
97
|
e.addEventListener( |
|
98
|
'click', |
|
99
|
function(ev){ |
|
100
|
ev.preventDefault(); |
|
101
|
ev.stopPropagation(); |
|
102
|
if(e.disabled) return; /* This check is probably redundant. */ |
|
103
|
const txt = extract.call(opt); |
|
104
|
if(txt && D.copyTextToClipboard(txt)){ |
|
105
|
e.dispatchEvent(new CustomEvent('text-copied',{ |
|
106
|
detail: {text: txt} |
|
107
|
})); |
|
108
|
} |
|
109
|
}, |
|
110
|
false |
|
111
|
); |
|
112
|
if('function' === typeof opt.oncopy){ |
|
113
|
e.addEventListener('text-copied', opt.oncopy, false); |
|
114
|
} |
|
115
|
/* Make sure the <button> contains a single nested <span>. */ |
|
116
|
if(e.childElementCount!=1 || e.firstChild.tagName!='SPAN'){ |
|
117
|
D.append(D.clearElement(e), D.span()); |
|
118
|
} |
|
119
|
return e; |
|
120
|
}; |
|
121
|
|
|
122
|
F.copyButton.defaultOptions = { |
|
123
|
cssClass: 'copy-button', |
|
124
|
oncopy: undefined, |
|
125
|
style: {/*properties copied as-is into element.style*/} |
|
126
|
}; |
|
127
|
|
|
128
|
})(window.fossil); |
|
129
|
|