Tuesday, September 27, 2022

PowerApps: Highlight Selected Item In Gallery

Hello Friends,
Welcome back with another post on PowerApps. Sometimes, the client asks that when a user clicks on any row in gallery, it should be highlighted. As of know, there is no direct feature. However, we can achieve using a very small piece of code. The expected outcome is-
Let's start-
  1. First create a list "EmployeeInfo" in SharePoint and have some records.
  2. Now, open the PowerApps maker portal and create a canvas app.
  3. There will a screen by default "Screen1". Rename it to "ScreenViewByExperience".
  4. Add the list "EmployeeInfo" as DataSource.
  5. Now, add a "Blank flexible height gallery" and rename it to ParentGallery.
  6. Update below properties of "ParentGallery"-
    1. X: 0
    2. Y: 100
    3. Width: ScreenViewByExperience.Width
    4. Height: ScreenViewByExperience.Height-ParentGallery.Y
    5. ItemsDistinct(EmployeeInfo,Experience.Value)
  7. Now, click on template edit icon and add a "Label" and another "Blank flexible height gallery".
  8. For Label, update below properties:
    1. Name: Label_Experience
    2. Text: ThisItem.Result
    3. X: 0
    4. Y: 0
    5. Width: Parent.Width
    6. Height: 40
    7. Fill: RGBA(0, 13, 75, 1)
    8. Color: RGBA(255, 255, 255, 1)
  9. The outcome will be-
  10. Similarly, update the properties of child gallery as below
    1. Name: ChildGallery
    2. X: 0
    3. Y: Label_Experience.Y+Label_Experience.Height
    4. Width: Parent.Width
    5. Height: Parent.TemplateHeight-Label_Experience.Y-Label_Experience.Height
    6. Items: Filter(EmployeeInfo,ThisItem.Result in Experience.Value)
    7. TemplateSize: 40
    8. TemplatePadding: 0
  11. The outcome will be-
  12. Now, edit the ChildGallery again and add 2 labels to show "First Name" and "Last Name". Link them with ThisItem.'First Name' and ThisItem.'Last Name'.
  13. We have the basic structure ready. Now, comes to the actual part.
  14. Click on App >> OnStart property and add below code.
    1. Set(vrSelectedItemID,0);
      Set(vrClickCounter,0);
      
  15. We have initialized 2 variables-
    1. vrSelectedItemID- It will capture the ID of the item, user has clicked and will be used to validate the upon next click whether the same item is re-clicked or other item is clicked.
    2. vrClickCounter- It will capture the count of click for a particular item
  16. Now, click on ChildGallery >> OnSelect property and write below code
    1. If(
          vrClickCounter = 0,
          Set(
              vrClickCounter,
              1
          );
          Set(
              vrSelectedItemID,
              ChildGallery.Selected.ID
          );
          ,
          If(
              vrClickCounter = 1 && vrSelectedItemID = ChildGallery.Selected.ID,
              Notify(
                  Concatenate(
                      ChildGallery.Selected.'First Name',
                      " ",
                      ChildGallery.Selected.'Last Name'
                  ),
                  Success
              );
              ,
              Set(
                  vrSelectedItemID,
                  ChildGallery.Selected.ID
              );
          )
      )
  17. The outcome is-
  18. Next, click on ChildGallery >> TemplateFill property and write below code.
    1. If(vrSelectedItemID = ThisItem.ID,RGBA(244, 196, 48, 1),RGBA(0, 0, 0, 0))
      
  19. The outcome is-
  20. Save the application, publish it and run/play.
  21. On Load-
  22. Clicked "Deepika Gupta"
  23. Clicked "Manoj Sharma"
  24. Re-Clicked "Manoj Sharma"
  25. This is how, you can achieve the functionality. If you want to navigate to another screen upon double click, then instead of using Notify, use Navigate function.
  26. In case, if you wish to give user the "Hand" experience upon hover, then replace the labels used in child gallery with buttons and update the properties as below-
  27. Color RGBA(0, 13, 75, 1)
    Fill Transparent
    BorderColor Transparent
    DisabledColor Self.Color
    DisabledFill Self.Fill
    DisabledBorderColor Self.BorderColor
    PressedColor Self.Color
    PressedFill Self.Fill
    PressedBorderColor Self.BorderColor
    HoverColor Self.Color
    HoverFill Self.Fill
    HoverBorderColor Self.BorderColor
    BorderStyle None
    BorderThickness 0
    OnSelect Select(Parent)
    BorderRadius 0
  28. After replacing labels with buttons, below is the outcome-
  29. As we can see, there is no difference in the outcome. However, when you hover the mouse upon the item, you will find the cursor converted into Hand.
With this, I am concluding this post.
Happy Coding !!!
Will see you again with some new topics.
Stay Safe !
Stay Healthy !

Monday, September 26, 2022

PowerShell: Search Particular Code In PowerApps

Hello Friends,

Welcome back with another post on PowerShell. Yes, PowerShell. Sometimes, we face scenario, where we have to find use of a particular control or code or flow in the entire app. If, the app is smaller, you may do it manually. However, if the app is too big as well as complex, then it would be a challenging task. Today, we going to provide a solution for this problem. We can perform this activity using PowerShell.

Let's start-

  1. Let's suppose, we have created below app in PowerApps.
  2. It is having one label, one text input and two buttons.
  3. Each button is calling a flow.
  4. Now, export this package and place it in a folder (for example folder named "SearchContent").
  5. Traverse this package as below-
    1. Package.zip >> Microsoft.PowerApps >> apps >> <Numeric ID> folder.
  6. Here, you will find JSONs, Images, MSAPP file and one more file.
  7. Copy the MSAPP file and paste at the root folder (SearchContent) location.
  8. Now copy the below PowerShell code and paste it in a notepad file.
    1. # Color Options: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, 
      # Color Options: DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
      
      ########################## INPUT PARAMETER ##########################
      	Param
      	(
      		[string] $solutionPath,
      		[string] $searchString
      	)
      
      ######################## FILE BASE  LOCATION ########################
      # Need to encode in proper UTF8
      	$locationPath = (Get-Location).Path
      	$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
      	[System.Environment]::CurrentDirectory = $locationPath
      
      ######################### GLOBAL  VARIABLES #########################
      	$solutionName = [System.IO.Path]::GetFileNameWithoutExtension($solutionPath)
      	$strNow = Get-Date -Format "yyyyMMdd_HHmmss"
      	$newSolutionFolder = "$($solutionName)_$($strNow)"
      	$resultTable = New-Object system.Data.DataTable 'OutPutDataTable'
      	$newcol = New-Object system.Data.DataColumn 'Sr No',([string]);
      	$resultTable.Columns.Add($newcol)
      	$newcol = New-Object system.Data.DataColumn 'Attribute Name',([string]);
      	$resultTable.Columns.Add($newcol)
      	$newcol = New-Object system.Data.DataColumn 'Attribute Value',([string]);
      	$resultTable.Columns.Add($newcol)
      	$intParentSrNo = 0
      
      ######################### SUPPORT FUNCTIONS #########################
      
      # ====> Parsing file and folder
      	function ParseFilesNFolders
      	{
      		param([string]$thisFolder)
      		$folderContent = Get-ChildItem -Path $thisFolder
      
      		foreach($content in $folderContent)
      		{
      			if($content.GetType() -eq [System.IO.DirectoryInfo])
      			{
      				ParseFilesNFolders -thisFolder "$thisFolder\$content"
      			}
      			else
      			{
      				$ext = [System.IO.Path]::GetExtension($content)
      
      				Switch($ext)
      				{
      					".json"
      					{
      						if($thisFolder -like "*Controls")
      						{
      							$row = $resultTable.NewRow()
      							$row.'Attribute Name' = ("")
      							$row.'Attribute Value' = ("")
      							$resultTable.Rows.Add($row)
      							$intParentSrNo++
      							$row = $resultTable.NewRow()
      							$row.'Sr No' = ($intParentSrNo)
      							$row.'Attribute Name' = ("File Name")
      							$row.'Attribute Value' = ("**** " + $content + " ****").ToUpper()
      							$resultTable.Rows.Add($row)
      
      							$json = Get-Content -Raw -Path "$thisFolder\$content" | ConvertFrom-Json
      							Get-LeafProperty $json
      						}
      					}
      					".msapp"
      					{
      						$msappFileName = [System.IO.Path]::GetFileNameWithoutExtension("$thisFolder\$content")
      						New-Item -Path ".\Temp\$newSolutionFolder" -Name $msappFileName -ItemType "directory" | Out-Null
      						Copy-Item "$thisFolder\$content" -Destination ".\Temp\$newSolutionFolder\$content"
      						Rename-Item ".\Temp\$newSolutionFolder\$content" "$msappFileName.zip"
      						Expand-Archive ".\Temp\$newSolutionFolder\$msappFileName.zip" -DestinationPath ".\Temp\$newSolutionFolder\$msappFileName"
      						Remove-Item -Path ".\Temp\$newSolutionFolder\$msappFileName.zip" -Force
      						Remove-Item -Path "$thisFolder\$content" -Force
      						ParseFilesNFolders -thisFolder ".\Temp\$newSolutionFolder\$msappFileName"
      						Compress-Archive -Path ".\Temp\$newSolutionFolder\$msappFileName\*" -DestinationPath "$thisFolder\$msappFileName.zip"
      						Rename-Item "$thisFolder\$msappFileName.zip" "$content"
      					}
      					Default
      					{
      					}
      				}
      			}
      		}
      	}
      
      	function Get-LeafProperty
      	{
      		param([Parameter(ValueFromPipeline)] [object] $InputObject, [string] $NamePath)
      		process
      		{
      			if ($null -eq $InputObject -or $InputObject -is [DbNull] -or $InputObject.GetType().IsPrimitive -or $InputObject.GetType() -in [string], [datetime], [datetimeoffset], [decimal], [bigint])
      			{
       				if($NamePath -like '*.Name' -and $NamePath -notlike '*.Template.Name')
      				{
      					if($NamePath -eq 'TopParent.Name')
      					{
      						$row = $resultTable.NewRow()
      						$row.'Attribute Name' = ("Screen Name")
      						$row.'Attribute Value' = ("**** " + ($InputObject).ToString() + " ***").ToUpper()
      						$resultTable.Rows.Add($row)
      					}
      					else
      					{
      						$row = $resultTable.NewRow()
      						$row.'Attribute Name' = ($NamePath)
      						$row.'Attribute Value' = ($InputObject)
      						$resultTable.Rows.Add($row)
      					}
      
      				}
      				if($InputObject -like '*' + $searchString + '*')
      				{
      					$row = $resultTable.NewRow()
      					$row.'Attribute Name' = ($NamePath)
      					$row.'Attribute Value' = ($InputObject)
      					$resultTable.Rows.Add($row)
      				}
      			}
      			elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [System.Collections.IDictionary])
      			{
      				$i = 0
      				foreach ($o in $InputObject)
      				{
      					Get-LeafProperty $o ($NamePath + '[' + $i++ + ']')
      				}
      			}
      			else
      			{
      				# A non-quasi-primitive scalar object or a dictionary:
      				# enumerate its properties / entries.
      
      				$props = if ($InputObject -is [System.Collections.IDictionary]) { $InputObject.GetEnumerator() } else { $InputObject.psobject.properties }
      				$sep = '.' * ($NamePath -ne '')
      				foreach ($p in $props)
      				{
      					Get-LeafProperty $p.Value ($NamePath + $sep + $p.Name)
      				}
      			}
      		}
      	}
      
      
      ###################### SCRIPT EXECUTION  POINT ######################
      
      	try
      	{
      		cls
      		Write-Host "PROCESS STARTED."
      		#Check if Output and Temp folder exist
      		If(Test-Path ".\Output")
      		{
      		}
      		else
      		{
      			New-Item -Path ".\" -Name "Output" -ItemType "directory" | Out-Null
      		}
      
      		If(Test-Path ".\Temp")
      		{
      		}
      		else
      		{
      			New-Item -Path ".\" -Name "Temp" -ItemType "directory" | Out-Null
      		}
      
      # Get file name and create folder in Output Temp file
      		New-Item -Path ".\Output" -Name $newSolutionFolder -ItemType "directory" | Out-Null
      		New-Item -Path ".\Temp" -Name $newSolutionFolder -ItemType "directory" | Out-Null
      
      # Copy file solution
      		Copy-Item $solutionPath ".\Output\$newSolutionFolder"
      
      # Parse file solution
      		ParseFilesNFolders -thisFolder ".\Output\$newSolutionFolder"
      	}
      	catch
      	{
      		# Write-Host "ERROR:" -ForegroundColor Red
      		# Write-Host $_.Exception.Message -ForegroundColor Red
      	}
      	$resultTable | Format-Table
      	Write-Host "PROCESS COMPLETED."
  9. Save the notepad file as "SearchContentInPowerAppsUsingDataTable.ps1".
  10. Now, open the PowerShell command prompt-
  11. Traverse to the folder "SearchContent', were you had saved the powershell script as wel as the MSAPP file.
  12. Now, use the below command
    1. .\SearchContentInPowerAppsUsingDataTable.ps1 -solutionPath .\N51de7fa8-8f94-4ee7-a1aa-3fa56fae5cc2-do
      cument.msapp -searchString "POC-"
      
  13. "-solutionPath" and "-searchString" are the input parameters of the Powershell script we have used here. "POC-" is the content we are searching for. Here you can use you own search string.
  14. Once done, press Enter.
  15. Above is the outcome of search. Let's interpret it. In PowerApps every code is saved against the attribute "InvariantScript".
  16. The "Name" attribute shown just before the "InvariantScript" is the name of control, in which the code is written.
  17. In above screenshot, we can see that there are two instances where our search string matches.
  18. It means, "Button1" and "Button2" are the controls, where we have to check our code.
  19. Now the next question, on which screen, these controls are placed?
  20. So the answer lies in the attribute "TopParent.Name".
  21. These controls are placed on "Screen1". In case, if we have multiple screens in the App, then this script, shows the controls screen wise. As we can see, in one of the other app, I had created, which was having multiple screens.
  22. If the app is too large and complex, then script execution may take time, In such cases, please be patient until the result displayed on screen. 
  23. This is how, you can find your code.
  24. If you want more precise results, where you can also look the attribute name upon which that particular code is written, then use below script:-
    1. # Color Options: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, 
      # Color Options: DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
      
      ########################## INPUT PARAMETER ##########################
      	Param
      	(
      		[string] $solutionPath,
      		[string] $searchString
      	)
      
      ######################## FILE BASE  LOCATION ########################
      # Need to encode in proper UTF8
      	$locationPath = (Get-Location).Path
      	$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
      	[System.Environment]::CurrentDirectory = $locationPath
      
      ######################### GLOBAL  VARIABLES #########################
      	$solutionName = [System.IO.Path]::GetFileNameWithoutExtension($solutionPath)
      	$strNow = Get-Date -Format "yyyyMMdd_HHmmss"
      	$newSolutionFolder = "$($solutionName)_$($strNow)"
      	$resultTable = New-Object system.Data.DataTable 'OutPutDataTable'	
      	$newcol = New-Object system.Data.DataColumn 'Attribute Name',([string]);
         	$resultTable.Columns.Add($newcol)
         	$newcol = New-Object system.Data.DataColumn 'Attribute Value',([string]);
         	$resultTable.Columns.Add($newcol)
       
      ######################### SUPPORT FUNCTIONS #########################
      
      # ====> Parsing file and folder
      	function ParseFilesNFolders
      	{
      		param([string]$thisFolder)
      		$folderContent = Get-ChildItem -Path $thisFolder
      
      		foreach($content in $folderContent)
      		{
      			if($content.GetType() -eq [System.IO.DirectoryInfo])
      			{
      				ParseFilesNFolders -thisFolder "$thisFolder\$content"
      			}
      			else
      			{
      				$ext = [System.IO.Path]::GetExtension($content)
      
      				Switch($ext)
      				{
      					".json"
      					{
      						if($thisFolder -like "*Controls")
      						{
                                  $row = $resultTable.NewRow()
                                  $row.'Attribute Name' = ("")
                                  $row.'Attribute Value' = ("")
                                  $resultTable.Rows.Add($row)
      
                                  $row = $resultTable.NewRow()
                                  $row.'Attribute Name' = ("File Name")
                                  $row.'Attribute Value' = ("**** " + $content + " ****")
                                  $resultTable.Rows.Add($row)
      
                                  $json = Get-Content -Raw -Path "$thisFolder\$content" | ConvertFrom-Json
      							Get-LeafProperty $json
      						}
      					}
      					".msapp"
      					{
      						$msappFileName = [System.IO.Path]::GetFileNameWithoutExtension("$thisFolder\$content")
      						New-Item -Path ".\Temp\$newSolutionFolder" -Name $msappFileName -ItemType "directory" | Out-Null
      						Copy-Item "$thisFolder\$content" -Destination ".\Temp\$newSolutionFolder\$content"
      						Rename-Item ".\Temp\$newSolutionFolder\$content" "$msappFileName.zip"
      						Expand-Archive ".\Temp\$newSolutionFolder\$msappFileName.zip" -DestinationPath ".\Temp\$newSolutionFolder\$msappFileName"
      						Remove-Item -Path ".\Temp\$newSolutionFolder\$msappFileName.zip" -Force
      						Remove-Item -Path "$thisFolder\$content" -Force
      						ParseFilesNFolders -thisFolder ".\Temp\$newSolutionFolder\$msappFileName"
      						Compress-Archive -Path ".\Temp\$newSolutionFolder\$msappFileName\*" -DestinationPath "$thisFolder\$msappFileName.zip"
      						Rename-Item "$thisFolder\$msappFileName.zip" "$content"
      					}
      					Default
      					{
      					}
      				}
      			}
      		}
      	}
      
      	function Get-LeafProperty
      	{
      		param([Parameter(ValueFromPipeline)] [object] $InputObject, [string] $NamePath)
      		process
      		{
      			if ($null -eq $InputObject -or $InputObject -is [DbNull] -or $InputObject.GetType().IsPrimitive -or $InputObject.GetType() -in [string], [datetime], [datetimeoffset], [decimal], [bigint])
      			{
      
      				if($NamePath -like 'TopParent.Name')
      				{
                          $row = $resultTable.NewRow()
                          $row.'Attribute Name' = ($NamePath)
                          $row.'Attribute Value' = ($InputObject)
                          $resultTable.Rows.Add($row)
      
      				}
                      if($NamePath -like '*.Name' -and $NamePath -notlike '*.Template.Name')
      				{
                          $Global:NameAttributeKey = ($NamePath)
                          $Global:NameAttributeValue = ($InputObject)
      
      				}
                      if($NamePath -like '*.Property')
      				{
                          $Global:PropertyAttributeKey = ($NamePath)
                          $Global:PropertyAttributeValue = ($InputObject)
      
      				}
      
      				if($InputObject -like '*' + $searchString + '*')
      				{
      
                          $row = $resultTable.NewRow()
                          $row.'Attribute Name' = ($Global:NameAttributeKey)
                          $row.'Attribute Value' = ($Global:NameAttributeValue)
                          $resultTable.Rows.Add($row)
      
                          $row = $resultTable.NewRow()
                          $row.'Attribute Name' = ($Global:PropertyAttributeKey)
                          $row.'Attribute Value' = ($Global:PropertyAttributeValue)
                          $resultTable.Rows.Add($row)
      
      
                          $row = $resultTable.NewRow()
                          $row.'Attribute Name' = ($NamePath)
                          $row.'Attribute Value' = ($InputObject)
                          $resultTable.Rows.Add($row)
      				}
      			}
      			elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [System.Collections.IDictionary])
      			{
      
      				$i = 0
      				foreach ($o in $InputObject)
      				{
      					Get-LeafProperty $o ($NamePath + '[' + $i++ + ']')
      				}
      			}
      			else
      			{
      				# A non-quasi-primitive scalar object or a dictionary:
      				# enumerate its properties / entries.
      
      				$props = if ($InputObject -is [System.Collections.IDictionary]) { $InputObject.GetEnumerator() } else { $InputObject.psobject.properties }
      				$sep = '.' * ($NamePath -ne '')
      				foreach ($p in $props)
      				{
      					Get-LeafProperty $p.Value ($NamePath + $sep + $p.Name)
      				}
      			}
      		}
      	}
      
      #####################################################################
      
      
      ###################### SCRIPT EXECUTION  POINT ######################
      
          $Global:PropertyAttributeKey =''
          $Global:PropertyAttributeValue =''
          $Global:NameAttributeKey =''
          $Global:NameAttributeValue =''
          
      	try
      	{
              cls
      		Write-Host "PROCESS STARTED."
      		#Check if Output and Temp folder exist
      		If(Test-Path ".\Output")
      		{
      		}
      		else
      		{
      			New-Item -Path ".\" -Name "Output" -ItemType "directory" | Out-Null
      		}
      
      		If(Test-Path ".\Temp")
      		{
      		}
      		else
      		{
      			New-Item -Path ".\" -Name "Temp" -ItemType "directory" | Out-Null
      		}
      
      # Get file name and create folder in Output Temp file
      		New-Item -Path ".\Output" -Name $newSolutionFolder -ItemType "directory" | Out-Null
      		New-Item -Path ".\Temp" -Name $newSolutionFolder -ItemType "directory" | Out-Null
      
      # Copy file solution
      		Copy-Item $solutionPath ".\Output\$newSolutionFolder"
      
      # Parse file solution
      		ParseFilesNFolders -thisFolder ".\Output\$newSolutionFolder"
      	}
      	catch
      	{
      		# Write-Host "ERROR:" -ForegroundColor Red
      		# Write-Host $_.Exception.Message -ForegroundColor Red
      	}
          $resultTable | Format-Table
          Write-Host "PROCESS COMPLETED."
      
  25. Executing this script will give below output:-
  26. Here, you can see that only those controls are fetched which is having the code, we are looking for. Additionally, it is also showing the property upon which the code was written.
  27. Clearly, Button1 & Button2 on Screen1 having the "POC-" code upon their "OnSelect" property.
  28. Similarly, if we execute the script on a complex application having multiple screens, then the outcome would be-
  29. The screens "Screen_GroupUsers", "Screen_IndividualUser", "Screen_GetUserAccounts" are not having the code we were looking for. Screen "Screen_DynamicColumnDisplay" is having the code upon "OnSelect" property of button "btnLoadDynamicColumnsTable" and upon "Items" property of datatable "DataTable5". Similarly, search result of other screens as well.
With this, I am concluding this post.
Happy Coding !!!
Will see you again with some new topics.
Stay Safe !
Stay Healthy !