如何通过 Microsoft Bot Framework 在 React WebChat 中制作菜单?
How to make a menu in React WebChat by Microsoft Bot Framework?
React webchat 提供上传附件的按钮。
我没有使用默认的附件按钮,而是尝试用一个可以切换菜单的按钮来替换它。此菜单将提供用户可以发送的各种文件类型,类似于下图中的文件类型。
但我无法想出一种方法来修改附件按钮的功能,甚至无法更改它。
请帮忙。
从技术上讲,这是可行的,但它有几个警告。首先,您正在玩 DOM ,这在 React 环境中是非常不鼓励的。其次,这里发生了很多事情来完成这项工作,如果某些组件、功能或元素在幕后发生变化,这确实会让您在未来出错。这里使用了许多元素,但是它们没有正确的 ID 或者它们依赖于生成的 class 名称。为了隔离某些元素,您将需要通过分配的名称或角色来定位它们……这很脆弱。 (我现在可以告诉您,已经计划重新设计发送框以适应即将推出的功能。)
在我进入细节之前,如果你真的想正确地做到这一点,我建议你克隆 BotFramework-WebChat repo 并在后台进行你自己的更改。这样你就不会直接使用 DOM 了,如果操作得当,将会产生一个更稳定的环境。您有责任使您的版本与官方版本保持同步。无论采用哪种方式,您都需要对代码进行维护以保持最新和正常工作。
这应该会让你朝着你想要的方向前进。请注意,这主要集中在网络聊天代码上。除此之外,我没有构建真正的功能。即使是第一个“功能”按钮(见下文),虽然允许您 select 一个文件,但只发送 activity 中的文件名。获取和发送文件以及您的机器人如何响应 activity 超出了您的提问范围,不会涉及。
辅助函数 - 用于简化创建要使用的不同元素。
const createElement = ( elType, id, className, option ) => {
const el = document.createElement( elType );
switch ( el.localName ) {
case 'button':
if ( id ) {
el.setAttribute( 'id', id );
el.setAttribute( 'type', 'button' );
}
if ( className ) {
el.classList.add( className );
}
return el;
case 'div':
if ( id ) {
el.setAttribute( 'id', id );
}
if (className) {
el.classList.add( className );
}
if (option) {
const style = option.style;
el.setAttribute( 'style', style );
}
return el;
case 'img':
if ( className ) {
el.classList.add( className );
}
if ( option ) {
const src = option;
el.src = src;
}
return el;
case 'input':
if ( id ) {
el.setAttribute( 'id', id );
}
if ( className ) {
el.className = className;
}
if ( option ) {
const inputName = option.name;
const inputType = option.type;
const style = option.style;
el.setAttribute( 'name', inputName );
el.setAttribute( 'type', inputType );
el.setAttribute( 'style', style );
}
return el;
case 'p':
if ( option ) {
const innerText = option;
el.innerText = innerText;
}
return el;
}
};
获取发送框 - 获取发送框并定位文件附件按钮。
const parent = document.querySelector( '.main' );
const attachmentButton = document.querySelector( '[title="Upload file"]' );
if ( attachmentButton ) {
var attachmentSVG = attachmentButton.firstElementChild;
var attachmentParent = attachmentButton.parentElement;
}
const child = parent.querySelectorAll( 'svg' );
创建菜单和“按钮”(输入) - 注意:第一个按钮 documentButton
的功能在于它允许您 select一份文件。其他五个按钮不起作用,这就是为什么它们的设计与第一个略有不同。 如上所述,在此示例中,selecting 文件仅发送文件名。
// Creates containers to hold menu and buttons
const menuContainer = createElement( 'div', 'menuContainer', undefined );
menuContainer.hidden = true;
const firstRow = createElement( 'div', 'firstRow' );
const secondRow = createElement( 'div', 'secondRow' );
const documentButton = createElement( 'div', 'documentBtn', 'menuItemSize', { style: 'display: none' } );
const inputFile = createElement( 'input', 'docFile', 'menuItemSize', { name: 'docFile', type: 'file', style: 'display: none' } );
const docImage = createElement( 'img', undefined, 'menuItemSize', './images/menuDoc.png' );
documentButton.appendChild( inputFile );
documentButton.appendChild( docImage );
const docLabel = createElement( 'p', undefined, undefined, 'Document' );
const docWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
docWrapper.appendChild( documentButton );
docWrapper.appendChild( docImage );
docWrapper.appendChild( docLabel );
firstRow.appendChild( docWrapper );
menuContainer.appendChild( firstRow );
// Enables button, allows file selection, and "sends" file via postBack.
docImage.onclick = () => { inputFile.click() };
inputFile.onchange = ( e ) => { handleChange( e ) };
const handleChange = ( e ) => {
const file = e.target.files[0];
store.dispatch({
type: 'WEB_CHAT/SEND_POST_BACK',
payload: {
value: { file: file.name }
}
})
};
const cameraButton = createElement( 'div', 'cameraBtn', 'menuItemSize' );
const cameraImage = createElement( 'img', undefined, 'menuItemSize', './images/menuCamera.png' );
cameraButton.appendChild( cameraImage );
const cameraLabel = createElement( 'p', undefined, undefined, 'Camera' );
const cameraWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
cameraWrapper.appendChild( cameraButton );
cameraWrapper.appendChild( cameraLabel );
firstRow.appendChild( cameraWrapper );
menuContainer.appendChild( firstRow );
const galleryButton = createElement( 'div', 'galleryBtn', 'menuItemSize' );
const galleryImage = createElement( 'img', undefined, 'menuItemSize', './images/menuGallery.png' );
galleryButton.appendChild( galleryImage);
const galleryLabel = createElement( 'p', undefined, undefined, 'Gallery' );
const galleryWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
galleryWrapper.appendChild( galleryButton );
galleryWrapper.appendChild( galleryLabel );
firstRow.appendChild( galleryWrapper );
menuContainer.appendChild( firstRow );
const audioButton = createElement( 'div', 'audioBtn', 'menuItemSize' );
const audioImage = createElement( 'img', undefined, 'menuItemSize', './images/menuAudio.png' );
audioButton.appendChild( audioImage );
const audioLabel = createElement( 'p', undefined, undefined, 'Audio' );
const audioWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
audioWrapper.appendChild( audioButton );
audioWrapper.appendChild( audioLabel );
secondRow.appendChild( audioWrapper );
menuContainer.appendChild( secondRow );
const locationButton = createElement( 'div', 'locationBtn', 'menuItemSize' );
const locationImage = createElement( 'img', undefined, 'menuItemSize', './images/menuLocation.png' );
locationButton.appendChild( locationImage );
const locationLabel = createElement( 'p', undefined, undefined, 'Location' );
const locationWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
locationWrapper.appendChild( locationButton );
locationWrapper.appendChild( locationLabel );
secondRow.appendChild( locationWrapper );
menuContainer.appendChild( secondRow );
const contactButton = createElement( 'div', 'contactBtn', 'menuItemSize' );
const contactImage = createElement( 'img', undefined, 'menuItemSize', './images/menuContact.png' );
contactButton.appendChild( contactImage );
const contactLabel = createElement( 'p', undefined, undefined, 'Contact' );
const contactWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
contactWrapper.appendChild( contactButton );
contactWrapper.appendChild( contactLabel );
secondRow.appendChild( contactWrapper );
menuContainer.appendChild( secondRow );
let transcriptWindow = document.querySelector( '[dir="ltr"]' );
let fosterParent = createElement( 'div' );
let menuButtonContainer = createElement( 'div', 'menuButtonContainer' );
let menuButton = createElement( 'div', 'menuButton' );
transcriptWindow.appendChild( menuContainer );
按钮功能
// Recreates file attachment button that, when clicked, opens the menu
menuButton.appendChild( attachmentSVG );
menuButtonContainer.appendChild( menuButton );
fosterParent.appendChild( menuButtonContainer );
const buttonDiv = createElement( 'div' );
buttonDiv.classList = attachmentButton.classList
buttonDiv.setAttribute( 'title', 'Upload file' );
buttonDiv.classList.remove( 'webchat__icon-button' );
attachmentButton.remove();
attachmentParent.appendChild( buttonDiv );
buttonDiv.innerHTML = fosterParent.innerHTML;
// Gets elements for use with event listeners
const menuBtnContainer = document.querySelector( '#menuButtonContainer' );
const menuItems = document.querySelectorAll( '.menuItemSize' );
const menuItemButtons = [];
menuItems.forEach(item => {
if ( item.localName === 'div' ) {
menuItemButtons.push( item )
}
});
// Shows/hides menu on file attachment button click
const menu = document.querySelector( '#menuContainer' );
menuBtnContainer.addEventListener('click', ( e ) => {
e.preventDefault();
switch ( menu.hidden ) {
case false:
menu.hidden = true;
break;
case true:
menu.hidden = false;
break;
}
return false;
});
// Hides menu when menu button is clicked
menuItemButtons.map(value => {
value.addEventListener('click', () => {
switch ( value.id ) {
case 'documentBtn':
menu.hidden = true;
break;
}
return false;
})
});
希望得到帮助!
React webchat 提供上传附件的按钮。
我没有使用默认的附件按钮,而是尝试用一个可以切换菜单的按钮来替换它。此菜单将提供用户可以发送的各种文件类型,类似于下图中的文件类型。
但我无法想出一种方法来修改附件按钮的功能,甚至无法更改它。
请帮忙。
从技术上讲,这是可行的,但它有几个警告。首先,您正在玩 DOM ,这在 React 环境中是非常不鼓励的。其次,这里发生了很多事情来完成这项工作,如果某些组件、功能或元素在幕后发生变化,这确实会让您在未来出错。这里使用了许多元素,但是它们没有正确的 ID 或者它们依赖于生成的 class 名称。为了隔离某些元素,您将需要通过分配的名称或角色来定位它们……这很脆弱。 (我现在可以告诉您,已经计划重新设计发送框以适应即将推出的功能。)
在我进入细节之前,如果你真的想正确地做到这一点,我建议你克隆 BotFramework-WebChat repo 并在后台进行你自己的更改。这样你就不会直接使用 DOM 了,如果操作得当,将会产生一个更稳定的环境。您有责任使您的版本与官方版本保持同步。无论采用哪种方式,您都需要对代码进行维护以保持最新和正常工作。
这应该会让你朝着你想要的方向前进。请注意,这主要集中在网络聊天代码上。除此之外,我没有构建真正的功能。即使是第一个“功能”按钮(见下文),虽然允许您 select 一个文件,但只发送 activity 中的文件名。获取和发送文件以及您的机器人如何响应 activity 超出了您的提问范围,不会涉及。
辅助函数 - 用于简化创建要使用的不同元素。
const createElement = ( elType, id, className, option ) => {
const el = document.createElement( elType );
switch ( el.localName ) {
case 'button':
if ( id ) {
el.setAttribute( 'id', id );
el.setAttribute( 'type', 'button' );
}
if ( className ) {
el.classList.add( className );
}
return el;
case 'div':
if ( id ) {
el.setAttribute( 'id', id );
}
if (className) {
el.classList.add( className );
}
if (option) {
const style = option.style;
el.setAttribute( 'style', style );
}
return el;
case 'img':
if ( className ) {
el.classList.add( className );
}
if ( option ) {
const src = option;
el.src = src;
}
return el;
case 'input':
if ( id ) {
el.setAttribute( 'id', id );
}
if ( className ) {
el.className = className;
}
if ( option ) {
const inputName = option.name;
const inputType = option.type;
const style = option.style;
el.setAttribute( 'name', inputName );
el.setAttribute( 'type', inputType );
el.setAttribute( 'style', style );
}
return el;
case 'p':
if ( option ) {
const innerText = option;
el.innerText = innerText;
}
return el;
}
};
获取发送框 - 获取发送框并定位文件附件按钮。
const parent = document.querySelector( '.main' );
const attachmentButton = document.querySelector( '[title="Upload file"]' );
if ( attachmentButton ) {
var attachmentSVG = attachmentButton.firstElementChild;
var attachmentParent = attachmentButton.parentElement;
}
const child = parent.querySelectorAll( 'svg' );
创建菜单和“按钮”(输入) - 注意:第一个按钮 documentButton
的功能在于它允许您 select一份文件。其他五个按钮不起作用,这就是为什么它们的设计与第一个略有不同。 如上所述,在此示例中,selecting 文件仅发送文件名。
// Creates containers to hold menu and buttons
const menuContainer = createElement( 'div', 'menuContainer', undefined );
menuContainer.hidden = true;
const firstRow = createElement( 'div', 'firstRow' );
const secondRow = createElement( 'div', 'secondRow' );
const documentButton = createElement( 'div', 'documentBtn', 'menuItemSize', { style: 'display: none' } );
const inputFile = createElement( 'input', 'docFile', 'menuItemSize', { name: 'docFile', type: 'file', style: 'display: none' } );
const docImage = createElement( 'img', undefined, 'menuItemSize', './images/menuDoc.png' );
documentButton.appendChild( inputFile );
documentButton.appendChild( docImage );
const docLabel = createElement( 'p', undefined, undefined, 'Document' );
const docWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
docWrapper.appendChild( documentButton );
docWrapper.appendChild( docImage );
docWrapper.appendChild( docLabel );
firstRow.appendChild( docWrapper );
menuContainer.appendChild( firstRow );
// Enables button, allows file selection, and "sends" file via postBack.
docImage.onclick = () => { inputFile.click() };
inputFile.onchange = ( e ) => { handleChange( e ) };
const handleChange = ( e ) => {
const file = e.target.files[0];
store.dispatch({
type: 'WEB_CHAT/SEND_POST_BACK',
payload: {
value: { file: file.name }
}
})
};
const cameraButton = createElement( 'div', 'cameraBtn', 'menuItemSize' );
const cameraImage = createElement( 'img', undefined, 'menuItemSize', './images/menuCamera.png' );
cameraButton.appendChild( cameraImage );
const cameraLabel = createElement( 'p', undefined, undefined, 'Camera' );
const cameraWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
cameraWrapper.appendChild( cameraButton );
cameraWrapper.appendChild( cameraLabel );
firstRow.appendChild( cameraWrapper );
menuContainer.appendChild( firstRow );
const galleryButton = createElement( 'div', 'galleryBtn', 'menuItemSize' );
const galleryImage = createElement( 'img', undefined, 'menuItemSize', './images/menuGallery.png' );
galleryButton.appendChild( galleryImage);
const galleryLabel = createElement( 'p', undefined, undefined, 'Gallery' );
const galleryWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
galleryWrapper.appendChild( galleryButton );
galleryWrapper.appendChild( galleryLabel );
firstRow.appendChild( galleryWrapper );
menuContainer.appendChild( firstRow );
const audioButton = createElement( 'div', 'audioBtn', 'menuItemSize' );
const audioImage = createElement( 'img', undefined, 'menuItemSize', './images/menuAudio.png' );
audioButton.appendChild( audioImage );
const audioLabel = createElement( 'p', undefined, undefined, 'Audio' );
const audioWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
audioWrapper.appendChild( audioButton );
audioWrapper.appendChild( audioLabel );
secondRow.appendChild( audioWrapper );
menuContainer.appendChild( secondRow );
const locationButton = createElement( 'div', 'locationBtn', 'menuItemSize' );
const locationImage = createElement( 'img', undefined, 'menuItemSize', './images/menuLocation.png' );
locationButton.appendChild( locationImage );
const locationLabel = createElement( 'p', undefined, undefined, 'Location' );
const locationWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
locationWrapper.appendChild( locationButton );
locationWrapper.appendChild( locationLabel );
secondRow.appendChild( locationWrapper );
menuContainer.appendChild( secondRow );
const contactButton = createElement( 'div', 'contactBtn', 'menuItemSize' );
const contactImage = createElement( 'img', undefined, 'menuItemSize', './images/menuContact.png' );
contactButton.appendChild( contactImage );
const contactLabel = createElement( 'p', undefined, undefined, 'Contact' );
const contactWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
contactWrapper.appendChild( contactButton );
contactWrapper.appendChild( contactLabel );
secondRow.appendChild( contactWrapper );
menuContainer.appendChild( secondRow );
let transcriptWindow = document.querySelector( '[dir="ltr"]' );
let fosterParent = createElement( 'div' );
let menuButtonContainer = createElement( 'div', 'menuButtonContainer' );
let menuButton = createElement( 'div', 'menuButton' );
transcriptWindow.appendChild( menuContainer );
按钮功能
// Recreates file attachment button that, when clicked, opens the menu
menuButton.appendChild( attachmentSVG );
menuButtonContainer.appendChild( menuButton );
fosterParent.appendChild( menuButtonContainer );
const buttonDiv = createElement( 'div' );
buttonDiv.classList = attachmentButton.classList
buttonDiv.setAttribute( 'title', 'Upload file' );
buttonDiv.classList.remove( 'webchat__icon-button' );
attachmentButton.remove();
attachmentParent.appendChild( buttonDiv );
buttonDiv.innerHTML = fosterParent.innerHTML;
// Gets elements for use with event listeners
const menuBtnContainer = document.querySelector( '#menuButtonContainer' );
const menuItems = document.querySelectorAll( '.menuItemSize' );
const menuItemButtons = [];
menuItems.forEach(item => {
if ( item.localName === 'div' ) {
menuItemButtons.push( item )
}
});
// Shows/hides menu on file attachment button click
const menu = document.querySelector( '#menuContainer' );
menuBtnContainer.addEventListener('click', ( e ) => {
e.preventDefault();
switch ( menu.hidden ) {
case false:
menu.hidden = true;
break;
case true:
menu.hidden = false;
break;
}
return false;
});
// Hides menu when menu button is clicked
menuItemButtons.map(value => {
value.addEventListener('click', () => {
switch ( value.id ) {
case 'documentBtn':
menu.hidden = true;
break;
}
return false;
})
});
希望得到帮助!