Search code examples
botframeworkadaptive-cardsadaptive-design

Bot framework - How can I create a Searchable List


I can understand that bot framework is not yet mature in terms of the controls it provides, I'm trying to display a list of options to the user. When the list is having more element, only the top 5 would be displayed plus an option to view more which would display all the items with a search text box to avoid the scrolling. Hence, I'm trying to implement a searchable list. Looked in the bot framework but the Prompts and adaptive cards are not very useful or if I have missed something would be great to get some help.

Note - I'm using bot framework v3

Thanks


Solution

  • I put this together. It does something similar to what you want to do. It lets you pass a list into it and then it shows a prompt dialog with a set number of options letting you choose a new search and next and previous.

    [Serializable]
    public class OptionsDialog<T> : IDialog<OptionsResult<T>>
        where T : class
    {
        private const string CurrentPage = "currentPage";
    
        private readonly IList<T> _items;
    
        private readonly Func<T, string> _descriptionFunc;
    
        private readonly Func<T, IReadOnlyList<string>> _synonymFunc;
    
        private readonly string _prompt;
    
        private int current = 0;
    
        /// <summary>
        /// 
        /// </summary>
        /// <param name="items">Must be a distinct list</param>
        /// <param name="descriptionFunction">Function to get the description for an option</param>
        /// <param name="synonymFunction">Function to get the synonyms for an option</param>
        /// <param name="prompt">Prompt for the options</param>
        public OptionsDialog(IList<T> items, Func<T, string> descriptionFunction, Func<T, IReadOnlyList<string>> synonymFunction, string prompt)
        {
            _items = items;
            _descriptionFunc = descriptionFunction;
            _synonymFunc = synonymFunction;
            _prompt = prompt;
        }
    
        public Task StartAsync(IDialogContext context)
        {
            if (_items.Count() == 0)
            {
                context.Done(new OptionsResult<T>(null, false));
            }
            else if (_items.Count() == 1)
            {
                context.Done(new OptionsResult<T>(_items.First(), false));
            }
            else
            {
                SendContactOptions(context, _items);
            }
    
            return Task.CompletedTask;
        }
    
        private void SendContactOptions(IDialogContext context, IList<T> items)
        {
            int maxContacts = int.Parse(ConfigurationManager.AppSettings["MaxContactsShown"]);
            bool addNext = false;
            bool addPrevious = false;
            if (items.Count > maxContacts)
            {
                if (current > 0)
                {
                    addPrevious = true;
                }
    
                if (items.Skip(current * maxContacts).Count() > maxContacts)
                {
                    addNext = true;
                }
    
                items = items.Skip(current * maxContacts)
                                   .Take(maxContacts)
                                   .ToList();
            }
    
            List<string> descriptions;
            Dictionary<string, IReadOnlyList<string>> choices;
    
            descriptions = items.Select(item => _descriptionFunc(item)).ToList();
    
            choices = items.ToDictionary<T, string,
                IReadOnlyList<string>>(item => item.ToString(), item => _synonymFunc(item));
    
            if (addNext)
            {
                descriptions.Add("More");
                choices.Add("More", new List<string>() { "Next" });
            }
    
            if (addPrevious)
            {
                descriptions.Add("Previous");
                choices.Add("Previous", new List<string>() { "Previous", "Last", "Back" });
            }
    
            descriptions.Add("New Search");
            descriptions.Add("Cancel");
    
            choices.Add("New Search", new List<string>() { "None", "None of these", "Next Search", "Search Again", "Search" });
            choices.Add("Cancel", new List<string>() { "Stop", "Quit" });
    
            IPromptOptions<string> promptOptions = new PromptOptionsWithSynonyms<string>(_prompt, choices: choices, descriptions: descriptions,
                tooManyAttempts: DialogHelper.TooManyAttemptsText);
            PromptDialog.Choice(context, SelectItemAsync, promptOptions);
        }
    
        private async Task SelectItemAsync(IDialogContext context, IAwaitable<object> result)
        {
            try
            {
                string contactName = await result as string;
    
                if (contactName == "Cancel")
                {
                    context.Done(new OptionsResult<T>(null, false, true));
                }
                else if (contactName == "New Search")
                {
                    context.Done(new OptionsResult<T>(null, true));
                }
                else if (contactName == "More")
                {
                    current++;
                    SendContactOptions(context, _items);
                }
                else if (contactName == "Previous")
                {
                    current--;
                    SendContactOptions(context, _items);
                }
                else
                {
                    T resultItem = _items.FirstOrDefault(c => c.ToString().ToLower() == contactName.ToLower());
    
                    if (resultItem != null)
                    {
                        DialogHelper.DeleteConversationItem(context, "searchContacts");
                        context.Done(new OptionsResult<T>(resultItem, false));
                    }
                    else
                    {
                        context.Done(new OptionsResult<T>(null, false));
                    }
                }
            }
            catch (TooManyAttemptsException)
            {
                context.Done(new OptionsResult<T>(null, false));
            }
        }
    }
    
    
    [Serializable]
    public class OptionsResult<T>
        where T : class
    {
        /// <summary>
        /// Result of the options
        /// </summary>
        public T Result { get; set; }
    
        /// <summary>
        /// Whether the parent dialog needs to search again
        /// </summary>
        public bool SearchAgain { get; set; }
    
        public bool IsCancelled { get; set; }
    
    
        public OptionsResult(T result, bool searchAgain, bool cancelled = false)
        {
            Result = result;
            SearchAgain = searchAgain;
            IsCancelled = cancelled;
        }
    }