Category: Programming

  • 2 Data Sources in a Power Apps Combo Box

    2 Data Sources in a Power Apps Combo Box

    I was recently building a Power App for a work project and needed to have a combo box display the primary text field from one data source, while having the secondary text field display from another data source. Here’s how to display 2 data sources in a Power Apps combo box:

    Combo Field Data Source in Power AppsCombo Field Secondary Text in Power Apps

    We will be using the AddColumns and LookUp functions to essentially find a record that matches our criteria, while joining two tables together.

    According to the documentation, here are the descriptions for each function respectively:

    The AddColumns function adds a column to a table, and a formula defines the values in that column. Existing columns remain unmodified.

    The formula is evaluated for each record of the table.

    The LookUp function finds the first record in a table that satisfies a formula. Use LookUp to find a single record that matches one or more criteria.

    To break it down: AddColumns(Table1, “New Column Name”, LookUp(Table2, ‘Column We Want to Look Up’ = Table2[@Table We Have to Compare Against], ‘Column We Want To Return’))

    In the example below, I’m adding a column named “Account Name” to the Projects table. In order to find my “Account Name”, I am using the LookUp function to search the Accounts table for a record that matches “Account ID = Projects[@Account]”. The last argument of LookUp means I only want to return the “Account Name” column from the Accounts table.

    AddColumns(
        Projects,
        "Account Name",
        LookUp(
            Accounts,
            'Account ID' = Projects[@Account],
            'Account Name'
        )
    )

    We’ve now added a new column called “Account Name” (from the Accounts table) to the Projects table in our combo box.

    Combo Field Secondary Text in Power Apps

    This can be confusing at first, but if you need to pull data and combine tables from 2 data sources in a Power Apps combo box, you now know how!

    Interested in more workplace automation? Check out this article on Creating a Resume Builder in Power Apps

  • Creating a Resume Builder in Power Apps

    Creating a Resume Builder in Power Apps

    There are three main parts to creating a resume builder in Power Apps. You will be learning PowerApps, SharePoint, and Power Automate (formerly Flow) which allow the user to create, edit, clone and delete resumes, as well as email a resume in PDF format. For the PDF version of the resume, you will need to know some HTML because we will use Power Automate to convert our data into HTML then to PDF.

    The final product looks something like this.

    Power Apps Main Screen

    Create Resume

    Before we begin, I want to stress the importance of naming elements and organization. I prefer to use camel case for most of my elements. This ensures you can reference your code as easily as possible, so please come up with a naming convention and stick to it!

    Part 1: SharePoint

    First, let’s create a new list in SharePoint to use for storing the resume data. I named my list “Resumes” and added some basic columns to start.

    SharePoint List Library

    Once you have a SharePoint list created, you are ready to move on to Power Apps!

    Part 2: Power Apps

    Create a new Power Apps “canvas app from blank”.

    Make Your Own App

    Your first screen should be added for you already. This page will consist of a gallery, some text labels, text inputs, buttons, and images. For the main elements, I have some text, a search box with filter icon, company logo and a button to create a new resume.

    Power Apps Main Screen

    The “Create New Resume” button’s “OnSelect” property sets a global variable named “NewResume” to true, creates a new form called “ResumeForm”, and navigates to the Edit Screen. We will get to this further in the tutorial.

    Set(NewResume, true); NewForm(ResumeForm); Navigate(EditScreen)

    For the sort button next to the search box, we simply toggle a variable named SortDescending1 between true/false. Note that this is not a global variable and will only work on the current screen.

    UpdateContext({SortDescending1: !SortDescending1})

    Once you create your gallery, it will ask for a data source. This will be the SharePoint list you created in step 1. For my gallery, I chose to create a 4-column layout consisting of an image, three text labels and three buttons.

    User Image

    For our user image, we will be using the Office365Users connector. The “Image” property of our image will find the user by email and if the user has a photo, display it…otherwise show a default image.

    If(Office365Users.UserPhotoMetadata(ThisItem.Email).HasPhoto,Office365Users.UserPhotoV2(ThisItem.Email), SampleImage)

    Text Labels

    Below is the “Text” property of the two text labels, respectively. “Title” is a default column, so I will be using it as the resume title. Even though my example image above shows the users name, this is really the title column. This ensures that when we clone resumes, we can set a title and distinguish between resumes.

    ThisItem.Title
    ThisItem.'Job Title'

    Update Button

    The following code is for the “OnSelect” property. If NewResume is true (i.e. we clicked the “Create New Resume” button), then set NewResume to false, then reset the form and navigate to the Edit Screen.

    If(NewResume, Set(NewResume, false); ResetForm(ResumeForm)); Navigate(EditScreen)
    

    This completes the main screen. As mentioned before, I like to name files descriptively, so that referencing them is easier. An example is shown below.

    Power Apps File Tree

    Edit Screen

    Now we move on to our edit screen (which also acts as our create screen). This screen consists of a text label, four buttons (1 hidden), and a form.

    Edit Screen

    The “Create Resume” label at the top will dynamically change based on if we are editing or creating a resume. Set the “Text” property to change the text based on the boolean value of NewResume.

    If(NewResume, "Create Resume", "Edit Resume")

    Now create a new form and set the DataSource to your SharePoint list. You can pull in all the fields you previously created and choose which to include in your form. For any multi-text fields, you will want to edit the field and select “Edit rich text” as the control type.

    Edit Rich Text

    Buttons

    There is much to look at here, so I highly suggest reading the documentation to learn more about these functions. The Save/Send button will do the following:
    1) Patch to update record data in our SharePoint list
    2) Set to set the value of a global variable named “LastRecord” (which gets the ID of the last record entered so that we can send the ID to Power Automate)
    3) Run the “SendResume” Flow using the last record ID
    4) Reset the form if creating a new resume
    5) Navigate back to the main screen and refresh the data.

    The “OnSelect” property looks like this.

    Patch(
        Resumes,
        If(
            NewResume,
            Defaults(Resumes),
            galleryResume.Selected
        ),
        {
            Title: txtTitle.Text,
            'First Name': txtFirstName.Text,
            'Last Name': txtLastName.Text,
            'Job Title': txtJobTitle.Text,
            Email: txtEmail.Text,
            'Work Phone': txtWorkPhone.Text,
            'Mobile Phone': txtMobilePhone.Text,
            LinkedIn: txtLinkedIn.Text,
            Twitter: txtTwitter.Text,
            About: txtAbout.HtmlText
        }
    );
    Set(
        LastRecord,
        If(
            NewResume,
            First(
                Sort(
                    Resumes,
                    ID,
                    SortOrder.Descending
                )
            ),
            galleryResume.Selected
        )
    );
    SendPlusResume.Run(
        LastRecord.ID
    );
    If(
        NewResume,
        ResetForm(ResumeForm)
    );
    Navigate(MainScreen);
    Refresh(Resumes);

    The Save button uses similar code, so you can simply copy/paste the above, then remove the following.

    Set(
        LastRecord,
        If(
            NewResume,
            First(
                Sort(
                    Resumes,
                    ID,
                    SortOrder.Descending
                )
            ),
            galleryResume.Selected
        )
    );
    SendPlusResume.Run(
        true,
        LastRecord.ID
    );

    The cancel button is simple, as it just navigates back to main screen.

    Navigate(MainScreen)

    For the hidden delete button, we want it to navigate to the main screen and refresh the data set after removing the current record from the SharePoint list.

    Navigate(MainScreen); Remove(Resumes, galleryResume.Selected); Refresh(Resumes)

    We are now ready to setup our flows inside of Power Automate. Start by clicking on the “Action” tab at the top of Power Apps, then Power Automate, then “Create a new flow”. Follow along in the next part.

    Part 3: Power Automate

    We will be creating two separate flows: one for sending/emailing our resume and the other for cloning our resume.

    Flow #1: Send Resume

    Here is a quick overview. We previously set the trigger inside Power Apps. The flow then gets the fields from our SharePoint list, as well as the user profile photo. It sends this data to a string variable (in which I have three). A plugin, Encodian, will also be used to correctly convert HTML to PDF, as the default provided by OneDrive doesn’t work the way we want. Afterwards, it stores that PDF on SharePoint so that we can view it later, and finally emails it to the user.

    Send Resume

    Get SharePoint List Fields (Get item)

    Get SharePoint List Fields

    Get User Photo

    Get User Photo

    Convert User Photo (Base64) (Initialize variable)

    Convert User Photo

    Header Image (if you have one) (Initialize variable)

    Header Image

    Create HTML (Initialize variable)

    Create HTML

    After pasting your HTML code, you can use the user profile photo or header image like so (CSS and HTML examples)

    Convert HTML to PDF (Encodian)

    Convert HTML to PDF

    Create file in SharePoint

    Create File in SharePoint

    Send an Email

    Send an Email

    Flow #2: Clone Resume

    Here is another overview. We set the trigger in Power Apps, get the SharePoint fields, duplicate the record, and report back to Power Apps.

    Clone Resume

    Get item

    Get Item

    Create item

    Make sure to fill in any fields you might want to duplicate. Then change the Title field to append ” – Copy” to the end.

    Create Item in SharePoint

    Final Notes

    Back in Power Apps, edit the “OnSelect” property of the Clone button, replacing “NameofYourFlow”.

    NameofYourFlow.Run(ThisItem.ID); Refresh(Resumes)

    And finally, edit the View button to launch the web URL of your SharePoint file that was created, making sure to include your naming convention so it knows which resume to point to.

    Launch("https://plusconsulting.sharepoint.com/sites/PlusMarketing/Shared%20Documents/General/Resumes/"&ThisItem.'Last Name'&"_Resume.pdf")

    Partial credit comes from Richard Burdes which allowed me the idea to create this for my employer.

  • How to Create Dynamic Email Templates

    How to Create Dynamic Email Templates

    Original Post Located on LinkedIn.

    Creating email templates are often a time-consuming task, especially if these templates need to have the same overall look but be repeated with different content for each recipient. When you consider the number of variables and data that change, the task can seem a bit daunting without some kind of automation. In this article, we show you how to create dynamic email templates using MySQL and the popular Handlebars.js templating engine. If the data already exists…there is no reason to recreate it. Instead, we can utilize a database to pull in the data we need, and Handlebars to populate our fields dynamically.

    To begin, you can learn more about Handlebar.js and how it’s implemented here. Let’s start by grabbing the data we need in our MySQL database and outputting to JSON (JavaScript Object Notation) format. This is the format that your data must be in for Handlebars to accept it.

    We will be creating 4 files for this project:

    • index.php (Main file)
    • data.json (Generated JSON file)
    • config.js (Configuration file)
    • template.html (Template file)

    Procedural Method

    index.php

    // Connect to database
    $host = 'localhost';
    $user = 'username';
    $pass = 'password';
    $db = 'database';
    
    $conn = mysqli_connect($host,$user,$pass,$db);
    
    // Check connection
    if (mysqli_connect_errno())
      {
      echo "Failed to connect to MySQL: " . mysqli_connect_error();
      }
    
    // Get event data
    $events = mysqli_query($conn,"SELECT * FROM events");
    
    // Initiate empty array
    $data_array = array();
    
    // Loop through event data
    while($row = mysqli_fetch_assoc($events)) {
    	$data_array["event"] = $row;
    }
    
    // Output JSON to file
    $fp = fopen('data.json', 'w');
    fwrite($fp, json_encode($data_array));
    fclose($fp);
    
    mysqli_close($conn);

    Object-Oriented Method

    index.php

    // Connect to database
    $host = 'localhost';
    $user = 'username';
    $pass = 'password';
    $db = 'database';
    
    $conn = new mysqli($host, $user, $pass, $db);
    
    // Check connection
    if ($conn->connect_error) {
    	die("Connection failed: " . $con->connect_error);
    }
    
    // Get event data
    $sql = "SELECT * FROM events";
    
    // Return results from query
    $events = $conn->query($sql);
    
    // Set empty array
    $data_array = array();
    
    // Set counter
    $counter = 0;
    $counter2 = 0;
    
    // Loop through event data
    while($row = $events->fetch_assoc()) {
    	$data_array["event"] = $row;
    }
    
    // Output JSON to file
    $fp = fopen('data.json', 'w');
    fwrite($fp, json_encode($data_array));
    fclose($fp);
    
    $conn->close();

     

    Here is an example of what our JSON file (data.json) might look like:

    {  
       "event":{  
          "name":"Conference",
          "date":"January 1, 2019",
       }
    }

     

    Now it’s time to add some HTML to our index.php file. We will include jQuery and Handlebars, as well as our config.js file.

    ...
    
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Template</title>
    </head>
    <body>
    	
    	<!-- Output HTML Template -->
    	<div id="external"></div>
    	
    	<!-- jQuery -->
    	<script	src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
    	<!-- Handlebars.js -->
    	<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.12/handlebars.min.js"></script>
    	<!-- Config -->
    	<script src="config.js"></script>
    	
    </body>
    </html>

     

    The next step is to setup and compile Handlebars inside our config.js file:

    $.when(
    
    	// Get JSON data
    	$.ajax({
    		dataType: "json",
    		url: "data.json"
    	}),
    	
    	// Get HTML template
    	$.ajax({
    		url: "template.html"
    	})
    
    ).done(function(data,html) {
    
    	var template = Handlebars.compile(html[0]);
    	$(template(data[0])).appendTo("#external");
    
    	}
    );

     

    template.html (This will be where your HTML template goes)

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Template</title>
    </head>
    <body>
    
    <!-- Simple Handlebars expression -->
    {{event.date}} 
    	
    </body>
    </html>

     

    The above expression {{event.date}} would return “January 1, 2019” from our JSON file.

    That’s about all there is to it! Now you can pull dynamic data from a MySQL database and insert those variables into your template.html file. I highly recommend reading more about the features of Handlebars.js and what it’s capable of doing. If you have any questions, please feel free to leave them in the comments!