Microsoft Bot framework session.endDialog() use - node.js

I read the Bot document somewhere people posted if you replace dialog then previous dialog store in stack and stored somewhere.
Now I tried to follow the way to endDialog() then replaceDialog();
callRequest.GetWebAPICall(session, urlData, function (body) {
if(body.statusCode == 200) {
if(body.data == undefined) {
builder.Prompts.choice(session,Want to Select List?", "Yes|No",{listStyle: builder.ListStyle.button});
} else {
session.endDialog();
session.replaceDialog('/Show List');
}
} else {
session.send('Something went wrong. You can use the back or top command.');
session.replaceDialog('/menu');
}
});
For need to know if I replace below lines
session.endDialog();
session.replaceDialog('/Show List');
by
session.endDialog('/Show List');

No. endDialog() doesn't have the functionality to start a new dialog. You can refer to the function definition interface endDialog(message?: TextOrMessageType, ...args: any\[\]): Session;.
In your case, '/Show List' would be sent to the user as a message.
And there is also a misunderstanding about replaceDialog().
Ends the current dialog and starts a new one its place. The parent dialog will not be resumed until the new dialog completes.
If you need to store the previous dialog, you can use beginDialog()

Related

dynamic prompt choices in bot-framework v4 (node.js)

I've got a prompt for an SMS bot in which the user can make multiple choices. I'm looking for a pattern for a ChoicePrompt that allows me to do this:
show multiple selections
then after the user selects and answer, re-prompt them to answer again
Remove their previous choice(s) and add an "exit" option to move on
Automatically end the step if they've selected everything.
I'd like to avoid creating a new prompt w/switch cases for each answer tier, as this pattern needs to be implemented in a lot of places...
Example:
bot: User, what do you do to relax?
Exercise
Read a book
Nothing
user: Exercise
bot: Exercise, cool. What else?
Read a book
Nothing else
user: Read a book
bot: OK, you've done everything so we're moving on!
The botframework don't have a ListPrompt that I can see, at least for v4. They do however, have Suggested Actions you can use for this!!! The Botbuilder-Samples repo has a Suggested Action sample that shows a list of three colors:
async onTurn(turnContext) {
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
if (turnContext.activity.type === ActivityTypes.Message) {
const text = turnContext.activity.text;
// Create an array with the valid color options.
const validColors = ['Red', 'Blue', 'Yellow'];
// If the `text` is in the Array, a valid color was selected and send agreement.
if (validColors.includes(text)) {
await turnContext.sendActivity(`I agree, ${ text } is the best color.`);
} else {
await turnContext.sendActivity('Please select a color.');
}
// After the bot has responded send the suggested actions.
await this.sendSuggestedActions(turnContext);
} else if (turnContext.activity.type === ActivityTypes.ConversationUpdate) {
await this.sendWelcomeMessage(turnContext);
} else {
await turnContext.sendActivity(`[${ turnContext.activity.type } event detected.]`);
}
}
An option would be to programatically create the array (in the example above, it's "const validColors") and if the reply is in the list of colors, recreate the array however you want without the chosen option.

Multiple buttons in HeroCard

I'd like to have multiple buttons on HeroCard
and be able to press all buttons one after another
but when I press click button program jumps to next function in waterfall
and expects next action instead of button action again
what should I do in this case?
bot.dialog("/showCards", [
(session) => {
const msg = new Message(session)
.textFormat(TextFormat.xml)
.attachmentLayout(AttachmentLayout.carousel)
.attachments([{
title: "title",
url: "https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png"
}].map(obj =>
new HeroCard(session)
.title(obj.title)
.images([
CardImage.create(session, obj.url)
.tap(CardAction.showImage(session, obj.url)),
])
.buttons([
CardAction.openUrl(session, obj.url),
CardAction.imBack(session, `click`, "Click"),
CardAction.imBack(session, `clack`, "Clack")
])
));
Prompts.choice(session, msg, ["click", "clack"]);
},
(session, results) => {
// todo use results.response.entity
}
]);
You could also use CardAction.dialogAction and link every button to a beginDialogAction.
let card = new builder.HeroCard(session)
.title(title)
.subtitle(subtitle)
.buttons([builder.CardAction.dialogAction(session, 'dialogAAction', 'dataYouNeedInDialogA', 'ButtonTitleA'), builder.CardAction.dialogAction(session, 'dialogBAction', 'dataYouNeedInDialogA', 'ButtonTitleB')]);
let msg = new builder.Message(session)
.attachments([card])
session.endDialog(msg);
// use one of these two to either end the dialog and start a new one or to stay in the current dialog and wait for user input
session.send(msg);
// don't forget to add the dialogs to your bot / library later in your code (outside your current dialog)
bot.dialog('dialogA', dialogA); // initialized somewhere in your code
bot.dialog('dialogB', dialogB);
bot.beginDialogAction('dialogAAction', 'dialogA');
bot.beginDialogAction('dialogBAction', 'dialogB', {
onSelectAction: (session, args, next) => {
// you might want to clear the dialogStack if the button is pressed. Otherwise, if the button is pressed multiple times, instances of dialogB are pilled up on the dialog stack.
session.clearDialogStack();
next();
}
});
In my opinion, this is the best way to achieve the behaviour you described so far. All buttons work whenever the user presses them, even if they scroll back in the conversation and press the same button again. The only trade-off is that you have to pass data to the new dialog and can not use dialogData throughout the whole flow. Nevertheless, I think it's worth it because ensures consistent UX throughout the usage of the bot.
Hope this helps. You can build click and clack dialogs, link them to actions and pass the data that you need. The user would be able to press click, clack, click and the bot would still work. :)
Use a switch-case in the ResumeAfter function, in the default case send the user to the previous function.

How to find out if a user is dragging a tab

I am trying to figure out, if a user is dragging a tab, any tab. I don't care which tab it is, I just need to know, if any tab is being dragged.
What is the best way to do this?
Please note: I asked a similar question. However, in that other question I wanted to know, when dragging stopped so I could perform my move operation. The solution given there (retrying until it works) doesn't seem to apply to this new question.
There is no way to tell whether any tab is being "dragged" (=mouse button held down on a tab).
If you want to know that a tab drag has occurred (opposed to "is about to happen"), then you could use the chrome.tabs.onMoved (moved within a tab) and/or chrome.tabs.onAttached / chrome.tabs.onDetached events.
I built a solution based on the fact that Chrome doesn't allow the moving of tabs in the window that contains a tab that is currently being dragged.
In this case, chrome.runtime.lastError.message will be Tabs cannot be edited right now (user may be dragging a tab).
I utilize this by getting the first tab of the focused window and moving it to its index. Because I use its own index, there isn't actually a visual change, when the operation succeeds.
var Chrome = {
isUserDragging: function (callback) {
chrome.windows.getAll({ populate: true }, function (windows) {
var window = windows.filter(function (x) {
return x.type === 'normal' && x.focused && x.tabs
&& x.tabs.length;
})[0];
if (window === undefined)
return;
var tab = window.tabs[0];
chrome.tabs.move(tab.id, { index: tab.index }, function () {
callback(
chrome.runtime.lastError !== undefined &&
chrome.runtime.lastError.message.indexOf('dragging') !== -1);
});
});
}
}
Usage would be:
Chrome.isUserDragging(function(userIsDragging) {
if(userIsDragging)
// do something
else
// do something else
});
Now, based on this, I built a polling mechanism using setTimeout that checks periodically if the user is still dragging and executes an action, when the user stopped dragging.
The full implementation can be seen here and it uses these two helper classes.

How do I track whether a context menu item is already created by my Chrome Extension?

There are only four methods for chrome.contextMenus:
create
update
remove
removeAll
I am wondering how do I check whether one menu is already created?
I tried this:
try {
chrome.contextMenus.update("byname", {});
} catch (e) {
// doesn't exist
}
But it seems the error cannot be caught (but shown in the console).
Thanks for any kind of tips!
Each chrome.contextMenus.create call returns an unique identifier. Store these identifiers in an array or hash to keep track of them.
This is a direct solution to anyone having the op's problem, based on the suggestion by Rob W. The idea is to maintain your own list of existing context menu id's.
By using these wrapper functions to maintain context menu entries, also the removal and updates are being kept track of (addressing Fuzzyma's comment).
Usage works like Chrome's own methods, eg. createContextMenu({id: "something"}, onclick). It works for me.
let contextMenus = {}
// method to create context menu and keep track of its existence
function createContextMenu() {
if (arguments[0] && arguments[0].id) {
// TODO: not sure if this will work properly, is creation synchronous or asynchrounous?
// take in to account calll back and the runtime error?
chrome.contextMenus[arguments[0].id] = chrome.contextMenus.create.apply(null, arguments);
}
}
function updateContextMenu() {
if (arguments[0] && contextMenus[arguments[0]]) {
chrome.contextMenus.update.apply(mull, arguments);
}
}
function removeContextMenu() {
if (arguments[0] && contextMenus[arguments[0]]) {
chrome.contextMenus.remove.apply(null, arguments);
contextMenus[arguments[0]] = undefined;
}
}
function contextMenuExists(id) {
return !!contextMenus[id];
}

click to replace value, shift + click to append value using jquery

I have a list with items.
When I click any of these items, I copy its id-value into a form text-field.
Everytime I click, it replaces the value, which is correct by default. But what I would like to add, is a way for the user to hold down a key on their keyboard, and when they then click, they just .append whatever they just clicked into the same form field.
Here's my jQuery-code I'm using for the first/default scenario:
$(function(){
$('ul#filter-results li').click(function(){
var from = $(this).attr('id'); // get the list ID and
$('input#search').val(from+' ').keyup(); // insert into text-field then trigger the search and
$('input#search').focus(); // make sure the field is focused so the user can start typing immediately
});
});
Is there a way to implement some sort of keyboard key-listener?
Something like:
if (e.shiftKey){
.append('this text instead')
}
haven't tried out to see if shiftKey is even any valid name here
shiftKey is of one of the properties of the event object and is valid to be used. try this:
$(document).on('keyup click', function(e){
if (e.shiftKey) {
$('input#search').focus()
$('input#search').val(e.target.id)
}
})
DEMO
$('ul#filter-results').on('click', 'li', function(e) {
if(e.shiftKey) {
do something;
} else {
do something else;
}
});
There is a jQuery plugin for extended click.
You could try that or see how they have done it and implement it yourself.
ExtendedClick plugin
Hope this helps.
This is what I ended up with:
I switched over to altKey because shiftKey marked a lot of text when I clicked.
Didn't do anything besides it doesn't look good...
$(function(){
$('ul#filter-results li').click(function(e){
var text = $(this).attr('id'); // get the ID
var input = $('#search'); // form field to insert text into
if (e.altKey){ input.val(input.val()+', '+text+' ').keyup(); } // fetch whatever is already there, and add some more
else { input.val(text+' ').keyup(); } // just replace whatever is already there
$('#search').focus();
});
});
Thanks for good suggestions...

Resources