Answers.Body: <p>Well, it feels really hacky and 2010, but I managed to get it to work using <code>hook_civicrm_pageRun</code>. My field exists entirely outside of angular and is controlled via jquery. Hopefully there is a better answer using angularjs, but this works as a hack.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-09-13T16:43:57.653Z
Answers.LastActivityDate: 2024-09-13T16:43:57.653Z
Answers.UserDisplayName: Jamie
Answers.Body: <p>At last. I figured out the more proper angular method for adding a new field to a core angular form and saving the data entered in the field.</p>
  <ol>
  <li>Add an angular module to your extension: <code>civix generate:angular-module</code></li>
  <li>Add your new field via <code>hook_civicrm_alterAngular()</code>:</li>
  </ol>
  <pre><code>function dynamic_theme_selector_civicrm_alterAngular($angular) {                                                                                              
    $angular-&gt;add(\Civi\Angular\ChangeSet::create('dts_theme_field')                                                                                        
      -&gt;alterHtml('~/afGuiEditor/config-form.html', function(phpQueryObject $doc) {                                                                                                                                   
        $element = &lt;&lt;&lt;END                                                                                     
        &lt;div class=&quot;form-group&quot; ng-if=&quot;editor.afform.is_public&quot; ng-controller=&quot;dtsThemeCtrl&quot;&gt;                                                                                                        
          &lt;label for=&quot;af_theme&quot;&gt;{{ ts('Select a theme') }}&lt;/label&gt;                                           
          &lt;div class=&quot;input-group&quot;&gt;                                                                                                              
            &lt;select id=&quot;af_theme&quot;                                                                                                                          
              ng-model=&quot;af_theme&quot;                                                                                                                      
              name=&quot;af_theme&quot;                                                                                                                                                                 
              class=&quot;form-control&quot;                                                                                                                                                              
              title=&quot;{{ ts('Select a theme') }}&quot;                                                             
              ng-options=&quot;theme for theme in af_theme_options&quot;                                                                                                 
              ng-change=&quot;save_theme()&quot;                                                                                                                                   
            &gt;                                                                                                                                                        
            &lt;/select&gt;                                                                                                                                                                                                 
          &lt;/div&gt;                                                                                                       
          &lt;p class=&quot;help-block&quot;&gt;Enter the theme you would like to use to display this form.&lt;/p&gt;                                                                                                               
        &lt;/div&gt;                                                                                               
  END;                                                                                                                                              
        $doc-&gt;append($element);                                                                                                                               
      })                                                                                                                                                  
    );                                                                                                                                                                                            
  }   
  </code></pre>
  <p>Important things to note in the code snippet above:</p>
  <ul>
  <li><code>~/afGuiEditor/config-form.html</code> is the core angular form I'm modifying</li>
  <li><code>dtsThemeCtrl</code> is the name of the controller that I will referencing.</li>
  <li><code>af_theme</code> is the name of the model that I will be saving.</li>
  </ul>
  <ol start="3">
  <li>Ensure the core form you are modifying requires your angular module via <code>hook_civicrm_angularModules</code>:</li>
  </ol>
  <pre><code>function dynamic_theme_selector_civicrm_angularModules(&amp;$angularModules) {                                                                              
    $angularModules['afGuiEditor']['requires'][] = 'crmDynamicThemeSelector';                                                                                                                     
  }
  </code></pre>
  <ol start="4">
  <li>Edit the angular module created in step one (in my case, <code>ang/crmDynamicThemeSelector.js</code>):</li>
  </ol>
  <pre><code>(function(angular, $, _) {                                                              
    angular.module('crmDynamicThemeSelector', CRM.angRequires('crmDynamicThemeSelector'));
    // Our only purpose is to extend the afGuidEditor so we can control the
    // theme field we insert via hook_civicrm_alterAngular.                             
    try {
      angular.module('afGuiEditor').controller('dtsThemeCtrl', function($scope, crmApi4) {
        $scope.af_theme_options = [];
                                
        $scope.init = function() {                                                                                                                                                                                       
          crmApi4('Setting', 'get', {                                                                                                                                                                                    
            // Get all possible customized formbuilder themes.                                                                                                                                                           
            select: [&quot;dynamic_theme_selector_chosen_formbuilder_themes&quot;]                                                                                                                                                 
          }).then(function(chosen_themes) {                                                                                                                                                                              
            var route = $scope.editor.afform.server_route;                                                                                                                                                               
            var chosen_theme = '';                                                                                                                                                                                       
            if (chosen_themes[0]['value'][route]) {                                                                                                                                                                      
              chosen_theme = chosen_themes[0]['value'][route];                                                                                                                                                           
            }                                                                                                                                                                                                            
            $scope.af_theme = chosen_theme;                                                                                                                                                                              
            // Get available themes to populate the drop down.                                                                                                                                                           
            return crmApi4('Dts', 'GetAvailableThemes', {});                                                                                                                                                             
          }).then(function(result) {                                                                                                                                                                                     
            Object.entries(result).forEach(([key, value]) =&gt; {                                                                                                                                                           
              $scope.af_theme_options.push(key);                                                                                                                                                                         
            });                                                                                                                                                                                                          
          });                                                                                                                                                                                                            
        };                                                                                                                                                                                                               
                                                                                                                                                                                                                       
        $scope.save_theme = function() {                                                                                                                                                                                 
          var route = $scope.editor.afform.server_route;                                                                                                                                                                 
          console.log(&quot;Updating route -&gt; theme&quot;, route, $scope.af_theme);                                                                                                                                                
          CRM.api4('Dts', 'UpdateSelectedTheme', {                                                                                                                                                                       
            'entity': 'formbuilder',                                                                                                                                                                                     
            'theme': $scope.af_theme,                                                                                                                                                                                    
            'id': route                                                                                                                                                                                                  
          });                                                                                                                                                                                                            
        };                                                                                                                                                                                                               
        $scope.init();                                                                                                                                                                                                   
      });
    }
    catch(err) {
      console.log(&quot;Error registered&quot;, err);
    }                                                                                                                                                                                                                
  })(angular, CRM.$, CRM._);  
  </code></pre>
  <p>Important things to note in the snippet above:</p>
  <ul>
  <li><code>angular.module('afGuiEditor')</code> references the angular module I'm modifying (it's the first part of the first argument passed to <code>alterHtml</code> above).</li>
  <li><code>controller('dtsThemeCtrl'</code> refers to the controller I named in the html snippet above.</li>
  <li><code>$scope.af_theme</code> is how I access the value of my field because I used <code>ng-model=&quot;af_theme&quot;</code></li>
  </ul>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-09-18T19:05:10.760Z
Answers.Text: Well done Jamie!
Answers.UserDisplayName: Coleman
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-09-18T13:14:14.437Z
Answers.LastActivityDate: 2024-09-20T13:56:41.620Z
Answers.LastEditDate: 2024-09-20T13:56:41.620Z
Answers.UserDisplayName: Jamie
Body: <p>I'm following up on the questions about embedding javascript in an angular form (<a href="https://civicrm.stackexchange.com/questions/48575/formbuilder-alterangular-embed-javascript/48576">FormBuilder: alterAngular embed javascript</a>), but specifically to do so with a core afform.</p>
  <p>I'm trying to build an extension that allows people to choose the drupal theme they want to use when displaying a public afform.</p>
  <p>Using <code>hook_civicrm_alterAngular()</code> I can easily insert the field to select the theme. But, I can't save the setting. Or to be more precise, I did manage to get the setting saved using &quot;onchange&quot; and some jquery code. But I couldn't get the field to display the current value for the theme. I think angular is somehow interfering with jquery when I try to update a value on the form (probably for the reasons coleman explains in the above answer).</p>
  <p>@coleman suggests overriding the form with a local copy, but I'm hesitant to do that for fear of diverging from any changes during an upgrade.</p>
  <p>I also tried using @demerit's trick (<a href="https://civicrm.stackexchange.com/a/31264/181">https://civicrm.stackexchange.com/a/31264/181</a>). But given coleman's warning about caching it didn't seem viable.</p>
  <p>I also tried shoving the angular code directly into the <code>hook_civicrm_alterAngular</code> call via the  tag, but that didn't seem to work because angularjs complained about the controller not being defined. I think it processed the form before the javascript code.</p>
  <p>So that's how I ended up with this question: Can I create an angular app that adds a new tab to the core afform form. There's a &quot;+&quot; button that lists all the things that can be added as tabs. Is there a way I can add my own?</p>
  <p>Thanks!</p>
  
Tags: <angular>
Title: How would I create an extension that adds a tab to a core afform?
ThreadText: # How would I create an extension that adds a tab to a core afform?
  
  (Asked by: username_0 on 2024-09-13)
  
  I'm following up on the questions about embedding javascript in an angular form ([FormBuilder: alterAngular embed javascript](https://civicrm.stackexchange.com/questions/48575/formbuilder-alterangular-embed-javascript/48576)), but specifically to do so with a core afform.
  
  I'm trying to build an extension that allows people to choose the drupal theme they want to use when displaying a public afform.
  
  Using `hook_civicrm_alterAngular()` I can easily insert the field to select the theme. But, I can't save the setting. Or to be more precise, I did manage to get the setting saved using "onchange" and some jquery code. But I couldn't get the field to display the current value for the theme. I think angular is somehow interfering with jquery when I try to update a value on the form (probably for the reasons coleman explains in the above answer).
  
  @coleman suggests overriding the form with a local copy, but I'm hesitant to do that for fear of diverging from any changes during an upgrade.
  
  I also tried using @demerit's trick (<https://civicrm.stackexchange.com/a/31264/181>). But given coleman's warning about caching it didn't seem viable.
  
  I also tried shoving the angular code directly into the `hook_civicrm_alterAngular` call via the tag, but that didn't seem to work because angularjs complained about the controller not being defined. I think it processed the form before the javascript code.
  
  So that's how I ended up with this question: Can I create an angular app that adds a new tab to the core afform form. There's a "+" button that lists all the things that can be added as tabs. Is there a way I can add my own?
  
  Thanks!
  
  **Tags:** angular
  
  
  ---
  
  ## 2 answers
  
  ### Answer by username_0 on 2024-09-18. Score: 4 | Accepted answer
  
  At last. I figured out the more proper angular method for adding a new field to a core angular form and saving the data entered in the field.
  
  Add an angular module to your extension: civix generate:angular-module
  Add your new field via hook_civicrm_alterAngular():
  
  function dynamic_theme_selector_civicrm_alterAngular($angular) {                                                                                              
    $angular->add(\Civi\Angular\ChangeSet::create('dts_theme_field')                                                                                        
      ->alterHtml('~/afGuiEditor/config-form.html', function(phpQueryObject $doc) {                                                                                                                                   
        $element = <<                                                                                                        
          {{ ts('Select a theme') }}                                           
                                                                                                                        
                                                                                                                                                                    
                                                                                                                                                                                                             
                                                                                                                 
          Enter the theme you would like to use to display this form.                                                                                                               
                                                                                                       
  END;                                                                                                                                              
        $doc->append($element);                                                                                                                               
      })                                                                                                                                                  
    );                                                                                                                                                                                            
  }   
  
  Important things to note in the code snippet above:
  
  ~/afGuiEditor/config-form.html is the core angular form I'm modifying
  dtsThemeCtrl is the name of the controller that I will referencing.
  af_theme is the name of the model that I will be saving.
  
  
  Ensure the core form you are modifying requires your angular module via hook_civicrm_angularModules:
  
  function dynamic_theme_selector_civicrm_angularModules(&$angularModules) {                                                                              
    $angularModules['afGuiEditor']['requires'][] = 'crmDynamicThemeSelector';                                                                                                                     
  }
  
  
  Edit the angular module created in step one (in my case, ang/crmDynamicThemeSelector.js):
  
  (function(angular, $, _) {                                                              
    angular.module('crmDynamicThemeSelector', CRM.angRequires('crmDynamicThemeSelector'));
    // Our only purpose is to extend the afGuidEditor so we can control the
    // theme field we insert via hook_civicrm_alterAngular.                             
    try {
      angular.module('afGuiEditor').controller('dtsThemeCtrl', function($scope, crmApi4) {
        $scope.af_theme_options = [];
                                
        $scope.init = function() {                                                                                                                                                                                       
          crmApi4('Setting', 'get', {                                                                                                                                                                                    
            // Get all possible customized formbuilder themes.                                                                                                                                                           
            select: ["dynamic_theme_selector_chosen_formbuilder_themes"]                                                                                                                                                 
          }).then(function(chosen_themes) {                                                                                                                                                                              
            var route = $scope.editor.afform.server_route;                                                                                                                                                               
            var chosen_theme = '';                                                                                                                                                                                       
            if (chosen_themes[0]['value'][route]) {                                                                                                                                                                      
              chosen_theme = chosen_themes[0]['value'][route];                                                                                                                                                           
            }                                                                                                                                                                                                            
            $scope.af_theme = chosen_theme;                                                                                                                                                                              
            // Get available themes to populate the drop down.                                                                                                                                                           
            return crmApi4('Dts', 'GetAvailableThemes', {});                                                                                                                                                             
          }).then(function(result) {                                                                                                                                                                                     
            Object.entries(result).forEach(([key, value]) => {                                                                                                                                                           
              $scope.af_theme_options.push(key);                                                                                                                                                                         
            });                                                                                                                                                                                                          
          });                                                                                                                                                                                                            
        };                                                                                                                                                                                                               
                                                                                                                                                                                                                       
        $scope.save_theme = function() {                                                                                                                                                                                 
          var route = $scope.editor.afform.server_route;                                                                                                                                                                 
          console.log("Updating route -> theme", route, $scope.af_theme);                                                                                                                                                
          CRM.api4('Dts', 'UpdateSelectedTheme', {                                                                                                                                                                       
            'entity': 'formbuilder',                                                                                                                                                                                     
            'theme': $scope.af_theme,                                                                                                                                                                                    
            'id': route                                                                                                                                                                                                  
          });                                                                                                                                                                                                            
        };                                                                                                                                                                                                               
        $scope.init();                                                                                                                                                                                                   
      });
    }
    catch(err) {
      console.log("Error registered", err);
    }                                                                                                                                                                                                                
  })(angular, CRM.$, CRM._);  
  
  Important things to note in the snippet above:
  
  angular.module('afGuiEditor') references the angular module I'm modifying (it's the first part of the first argument passed to alterHtml above).
  controller('dtsThemeCtrl' refers to the controller I named in the html snippet above.
  $scope.af_theme is how I access the value of my field because I used ng-model="af_theme"
  
  > **Comments:**
  > **username_1**: Well done username_0!  
  
  ---
  
  ### Answer by username_0 on 2024-09-13. Score: 0
  
  Well, it feels really hacky and 2010, but I managed to get it to work using `hook_civicrm_pageRun`. My field exists entirely outside of angular and is controlled via jquery. Hopefully there is a better answer using angularjs, but this works as a hack.
  
  
  ---
  
  