AppExchange Package Upload to Include Custom Themes

After Banzai’ing our application and uploading it to AppExchange for Lightning certification we noticed that is does not include our custom theme and Font Awesome icons. Should we adjust our post install script? This is our Post Install Script.

public without sharing class StockCheckInstallScript implements InstallHandler {

       public static final String NAMESPACE_PREFIX = 'gbc';
    public static boolean IsRunning { 
        public get {
            if (IsRunning==null) IsRunning = false;
            return IsRunning;
        }
        public set; 
    }
    
    public void onInstall(InstallContext ctx) {
        IsRunning=true;
        RefreshPagesInModule(NAMESPACE_PREFIX);
        IsRunning=false;
    }
    
    public static List<skuid__Page__c> RefreshPagesInModule(String module) {
        
        // See if a StaticResource containing new pages for this module yet exists      
        StaticResource sr = [
            select Body
            from StaticResource 
            where Name = :(module + 'Pages')
            and ((NamespacePrefix = NULL) OR (NamespacePrefix = :module))
            limit 1
        ];
        
        // The new Pages for our module that we will be inserting       
        List<skuid__Page__c> newPages
             = (List<skuid__Page__c>) JSON.deserialize(sr.Body.toString(),List<skuid__Page__c>.class);
        List<Schema.SObjectField> layoutFields = new List<Schema.SObjectField>{
            skuid__Page__c.skuid__Layout__c,
            skuid__Page__c.skuid__Layout2__c,
            skuid__Page__c.skuid__Layout3__c,
            skuid__Page__c.skuid__Layout4__c,
            skuid__Page__c.skuid__Layout5__c
        };
        
        for (skuid__Page__c p : newPages) {
            // Get rid of the Ids so that upsert will proceed           
            p.Id = null;
            // Ensure that unused Layout fields are set to null         
            for (Schema.Sobjectfield f : layoutFields) {
            if (p.get(f)==null) p.put(f,null);
            }
        }
        
        // If we have successfully compiled new Pages for this module,        
       // delete the old ones and replace them with the new.        
       if (newPages != null &amp;&amp; !newPages.isEmpty()) {
        Schema.SObjectField f = skuid__Page__c.skuid__UniqueId__c;
        List<Database.UpsertResult> cr = Database.upsert(newPages,f,false);
        }   
        return newPages;
    }
       
} 

Hi Greg,

Here is a modified InstallScript that contains methods which should enable you to auto-create skuid__Theme__c records when installing your package into a customer’s org. In this example I’m assuming that you’re only including one Theme with your package, whose Name is “StockCheck” and which is stored in a Static Resource that is included in your package(very important!) called “StockCheckTheme”. This is just an example, you’ll need to to adjust to be whatever your Theme’s Name and Static Resource Name are.

Also, this post-install script does NOT automatically set the Org-Default Theme preference or change any Profile/User theme settings to be your Theme. If you want to do this this is possible but would take some more code.

 

public without sharing class StockCheckInstallScript implements InstallHandler {
    public static final String NAMESPACE_PREFIX = ‘gbc’;
    
    public static boolean IsRunning { 
        public get {
            if (IsRunning==null) IsRunning = false;
            return IsRunning;
        }
        public set; 
    }
    
    public void onInstall(InstallContext ctx) {
        IsRunning=true;
        RefreshPagesInModule(NAMESPACE_PREFIX);
        CreateDefaultThemes();
        IsRunning=false;
    }
    
    public static List RefreshPagesInModule(String module) {
        
        // See if a StaticResource containing new pages for this module yet exists      
        StaticResource sr = [
            select Body
            from StaticResource 
            where Name = :(module + ‘Pages’)
            and ((NamespacePrefix = NULL) OR (NamespacePrefix = :module))
            limit 1
        ];
        
        // The new Pages for our module that we will be inserting       
        List newPages
             = (List) JSON.deserialize(sr.Body.toString(),List.class);
        List layoutFields = new List{
            skuid__Page__c.skuid__Layout__c,
            skuid__Page__c.skuid__Layout2__c,
            skuid__Page__c.skuid__Layout3__c,
            skuid__Page__c.skuid__Layout4__c,
            skuid__Page__c.skuid__Layout5__c
        };
        
        for (skuid__Page__c p : newPages) {
            // Get rid of the Ids so that upsert will proceed           
            p.Id = null;
            // Ensure that unused Layout fields are set to null         
            for (Schema.Sobjectfield f : layoutFields) {
            if (p.get(f)==null) p.put(f,null);
            }
        }
        
        // If we have successfully compiled new Pages for this module,        
       // delete the old ones and replace them with the new.        
       if (newPages != null && !newPages.isEmpty()) {
        Schema.SObjectField f = skuid__Page__c.skuid__UniqueId__c;
        List cr = Database.upsert(newPages,f,false);
        }   
        return newPages;
    }
    
    // Returns a Map containing the definitions for expected default themes
   public static Map> GetDefaultThemes() {
      return new Map>{
         // Desktop Themes
         ‘StockCheck’    => new Map{ 
            ‘type’ => ‘Desktop’, 
            ‘resourceName’ => ‘StockCheckTheme’ 
         }
      };
   }
      
   // Create our default, expected Theme custom setting records if they don’t exist 
   public static void CreateDefaultThemes() {
      
      Map existingThemes = skuid__Theme__c.getAll();
      Map> defaultThemes = GetDefaultThemes();
      Set themeNamesToCreate = new Set();
      for (String themeName : defaultThemes.keyset()) {
         if (!existingThemes.containsKey(themeName)) {
            themeNamesToCreate.add(themeName); 
         }  
      }
      
      // If there are some default themes that do not exist in this org,
      // create them!
      if (!themeNamesToCreate.isEmpty()) {
         List themesToCreate = new List();
         for (String themeName : themeNamesToCreate) {
            Map themeDef = defaultThemes.get(themeName);
            themesToCreate.add(new skuid__Theme__c(
               Name                    = themeName,
               skuid__Resource_Namespace__c  = NAMESPACE_PREFIX,
               skuid__Resource_Name__c       = themeDef.get(‘resourceName’),
               skuid__Active__c           = true,
               skuid__Type__c                = themeDef.get(‘type’)
            ));
         }
         insert themesToCreate;  
      }
   }
       
}

Hi Zach.  

Thanks for the reply and your time.  
I am getting the following error when saving the script

"Error: Compile Error: Method does not exist or incorrect signature: CreateThemes() at line 15 column 9"

I tried a few fumbling changes but no joy.

Thanks again - G

Oops, copy and paste error. I edited the script above, try copying it again and resaving.

Thanks Zach.  

Singing like Skynnard!

I’m actually getting the same CreateThemes() error despite using the code above. Am I missing something?

public without sharing class StockCheckInstallScript implements InstallHandler { public static final String NAMESPACE_PREFIX = ‘gbc’;

public static boolean IsRunning { 
    public get {
        if (IsRunning==null) IsRunning = false;
        return IsRunning;
    }
    public set; 
}

public void onInstall(InstallContext ctx) {
    IsRunning=true;
    RefreshPagesInModule(NAMESPACE_PREFIX);
    CreateThemes();
    IsRunning=false;
}

public static List<skuid__Page__c> RefreshPagesInModule(String module) {
    
    // See if a StaticResource containing new pages for this module yet exists      
    StaticResource sr = [
        select Body
        from StaticResource 
        where Name = :(module + 'Pages')
        and ((NamespacePrefix = NULL) OR (NamespacePrefix = :module))
        limit 1
    ];
    
    // The new Pages for our module that we will be inserting       
    List<skuid__Page__c> newPages
         = (List<skuid__Page__c>) JSON.deserialize(sr.Body.toString(),List<skuid__Page__c>.class);
    List<Schema.SObjectField> layoutFields = new List<Schema.SObjectField>{
        skuid__Page__c.skuid__Layout__c,
        skuid__Page__c.skuid__Layout2__c,
        skuid__Page__c.skuid__Layout3__c,
        skuid__Page__c.skuid__Layout4__c,
        skuid__Page__c.skuid__Layout5__c
    };
    
    for (skuid__Page__c p : newPages) {
        // Get rid of the Ids so that upsert will proceed           
        p.Id = null;
        // Ensure that unused Layout fields are set to null         
        for (Schema.Sobjectfield f : layoutFields) {
        if (p.get(f)==null) p.put(f,null);
        }
    }
    
    // If we have successfully compiled new Pages for this module,        
   // delete the old ones and replace them with the new.        
   if (newPages != null &amp;&amp; !newPages.isEmpty()) {
    Schema.SObjectField f = skuid__Page__c.skuid__UniqueId__c;
    List<Database.UpsertResult> cr = Database.upsert(newPages,f,false);
    }   
    return newPages;
}

// Returns a Map containing the definitions for expected default themes

public static Map<String,Map<String,String>> GetDefaultThemes() {
return new Map<String,Map<String,String>>{
// Desktop Themes
‘StockCheck’ => new Map<String,String>{
‘type’ => ‘Desktop’,
‘resourceName’ => ‘StockCheckTheme’
}
};
}

// Create our default, expected Theme custom setting records if they don’t exist
public static void CreateDefaultThemes() {

  Map<String,skuid__Theme__c> existingThemes = skuid__Theme__c.getAll();
  Map<String,Map<String,String>> defaultThemes = GetDefaultThemes();
  Set<String> themeNamesToCreate = new Set<String>();
  for (String themeName : defaultThemes.keyset()) {
     if (!existingThemes.containsKey(themeName)) {
        themeNamesToCreate.add(themeName); 
     }  
  }
  
  // If there are some default themes that do not exist in this org,
  // create them!
  if (!themeNamesToCreate.isEmpty()) {
     List<skuid__Theme__c> themesToCreate = new List<skuid__Theme__c>();
     for (String themeName : themeNamesToCreate) {
        Map<String,String> themeDef = defaultThemes.get(themeName);
        themesToCreate.add(new skuid__Theme__c(
           Name                    = themeName,
           skuid__Resource_Namespace__c  = NAMESPACE_PREFIX,
           skuid__Resource_Name__c       = themeDef.get('resourceName'),
           skuid__Active__c           = true,
           skuid__Type__c                = themeDef.get('type')
        ));
     }
     insert themesToCreate;  
  }

}

}

I’m not sure why my change didn’t take last time—i changed it again. In the “onInstall” method, the method call should be “CreateDefaultThemes()” not CreateThemes.

Zach, since updating to this, my pages haven’t been auto-deploying. The theme has, which is great. Not sure if you see anything below, I don’t think I changed anything that would affect pages not unpacking. I can still manually unpack them, so its not a permissions or static resource issue. Thoughts?

public without sharing class InstallScript_CheetahBMS implements InstallHandler {
public static final String NAMESPACE_PREFIX = ‘CheetahBMS’;
public static boolean IsRunning {
public get {
if (IsRunning==null) IsRunning = false;
return IsRunning;
}
public set;
}

public void onInstall(InstallContext ctx) {
    IsRunning=true;
    RefreshPagesInModule(NAMESPACE_PREFIX);
    CreateDefaultThemes();
    IsRunning=false;
}

public static List<skuid__Page__c> RefreshPagesInModule(String module) {
    
    // See if a StaticResource containing new pages for this module yet exists      
    StaticResource sr = [
        select Body
        from StaticResource 
        where Name = :(module + 'Pages')
        and ((NamespacePrefix = NULL) OR (NamespacePrefix = :module))
        limit 1
    ];
    
    // The new Pages for our module that we will be inserting       
    List<skuid__Page__c> newPages
         = (List<skuid__Page__c>) JSON.deserialize(sr.Body.toString(),List<skuid__Page__c>.class);
    List<Schema.SObjectField> layoutFields = new List<Schema.SObjectField>{
        skuid__Page__c.skuid__Layout__c,
        skuid__Page__c.skuid__Layout2__c,
        skuid__Page__c.skuid__Layout3__c,
        skuid__Page__c.skuid__Layout4__c,
        skuid__Page__c.skuid__Layout5__c
    };
    
    for (skuid__Page__c p : newPages) {
        // Get rid of the Ids so that upsert will proceed           
        p.Id = null;
        // Ensure that unused Layout fields are set to null         
        for (Schema.Sobjectfield f : layoutFields) {
        if (p.get(f)==null) p.put(f,null);
        }
    }
    
    // If we have successfully compiled new Pages for this module,        
   // delete the old ones and replace them with the new.        
   if (newPages != null &amp;&amp; !newPages.isEmpty()) {
    Schema.SObjectField f = skuid__Page__c.skuid__UniqueId__c;
    List<Database.UpsertResult> cr = Database.upsert(newPages,f,false);
    }   
    return newPages;
}

    // Returns a Map containing the definitions for expected default themes

public static Map<String,Map<String,String>> GetDefaultThemes() {
return new Map<String,Map<String,String>>{
// Desktop Themes
‘CheetahOrange’ => new Map<String,String>{
‘type’ => ‘Desktop’,
‘resourceName’ => ‘CheetahOrangeTheme_2’
}
};
}

// Create our default, expected Theme custom setting records if they don’t exist
public static void CreateDefaultThemes() {

  Map<String,skuid__Theme__c> existingThemes = skuid__Theme__c.getAll();
  Map<String,Map<String,String>> defaultThemes = GetDefaultThemes();
  Set<String> themeNamesToCreate = new Set<String>();
  for (String themeName : defaultThemes.keyset()) {
     if (!existingThemes.containsKey(themeName)) {
        themeNamesToCreate.add(themeName); 
     }  
  }
  
  // If there are some default themes that do not exist in this org,
  // create them!
  if (!themeNamesToCreate.isEmpty()) {
     List<skuid__Theme__c> themesToCreate = new List<skuid__Theme__c>();
     for (String themeName : themeNamesToCreate) {
        Map<String,String> themeDef = defaultThemes.get(themeName);
        themesToCreate.add(new skuid__Theme__c(
           Name                    = themeName,
           skuid__Resource_Namespace__c  = NAMESPACE_PREFIX,
           skuid__Resource_Name__c       = themeDef.get('resourceName'),
           skuid__Active__c           = true,
           skuid__Type__c                = themeDef.get('type')
        ));
     }
     insert themesToCreate;  
  }

}
}

That’s very odd. I would have thought that you’d get no pages and no themes given how your code is ordered. I’m not sure.

So…what would be the proper order? :slight_smile:

I looked at the example and mine and am not seeing the difference

Can anyone spot my error above? Like Zach mentioned, I’m not getting themes or pages and for the life of me I can’t see what I did differently than before when pages worked fine.

So to confirm your onInstall method looks like this:

public void onInstall(InstallContext ctx) {
        IsRunning=true;
        RefreshPagesInModule(NAMESPACE_PREFIX);
        CreateDefaultThemes();
        IsRunning=false;
    }

But when you run this, your Themes are created but Pages are not updated or modified?

If that’s true, which is bizarre, I would try removing the call to CreateDefaultThemes(); from onInstall and then uploading a new version of your package and then trying to install it and see if your pages get updated as you’d expect. Basically go back to making sure that the page modifications are working as expected, then re-introduce the CreateDefaultThemes() part of the equation. I think it will be important in troubleshooting this to first establish that both routines are running as expected independently, then testing them running in conjunction. My guess is that something is preventing the RefreshPagesInModule functionality from working that is unrelated to creating default themes. But it is difficult to say.

It seems the template packaging was a red hearing. I stripped out the theme portion and reverted back to just unpacking pages and am having the problem only with child pages.

My master page and any pages that are not child pages unpack and update just fine. The child pages do not update at all via the post install script and are untouched. Manually I can unpack the pages from the static resource and it works, so I know its not a problem with the static resource. 

What I don’t get is if these pages are already created in the org I’m trying to update, why would they not be found and updated regardless of if they’re child pages?

Aha! That’s the issue. The code above would need to adjusted to handle linking Child Pages with their Master Page in the target org. We will get back to you on a solution for handling this.

Awesome thanks! Glad to know I’m not entirely crazy

Hey Zach, any update here? Now that all of our package’s pages are child pages of our master header, we aren’t able to update pages via post install script.