CiviCRM Community Forums (archive)

*

News:

Have a question about CiviCRM?
Get it answered quickly at the new
CiviCRM Stack Exchange Q+A site

This forum was archived on 25 November 2017. Learn more.
How to get involved.
What to do if you think you've found a bug.



  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion (Moderator: Donald Lobo) »
  • Including custom templates through hooks
Pages: [1] 2

Author Topic: Including custom templates through hooks  (Read 4632 times)

Rajan Mayekar

  • I post frequently
  • ***
  • Posts: 177
  • Karma: 20
    • Rajan's Blogs
Including custom templates through hooks
March 29, 2011, 09:34:45 pm
I was searching for the hooks/apis that allows to me to add elements in the tpl file, through my custom module ( by implementing hooks like hook_civicrm_pageRun( ), hook_civicrm_buildForm()) , without using custom templates. Since there may be a case where we  already have custom templates. I looked at  $beginHookFormElements but it just allows me to add form elements only, but not the custom html code, also this looks very restricted like we can not use this feature for two the different module which want add some elements in same tpl file ( ? ).

I think we should have some feature so that any module can include their own template anywhere it needs. So I tried to figure out this and came with following patch.This will allows us to include own template files by implementing hook_civicrm_pageRun( ) and hook_civicrm_buildForm() in the drupal module  8) .
Patch is just for drupal but using same way we can do it for joomla also.
Code: [Select]
Index: CRM/Core/Form.php
===================================================================
--- CRM/Core/Form.php    (revision 32975)
+++ CRM/Core/Form.php    (working copy)
@@ -339,6 +339,14 @@
                                    $this );
 
         $this->addRules();
+           
+        if ( isset($this->beingHookTemplatesAbove) && !empty($this->beingHookTemplatesAbove) ) {
+            $this->assign( 'beingHookTemplatesAbove', $this->beingHookTemplatesAbove );
+        }
+       
+        if ( isset($this->beingHookTemplatesDown) && !empty($this->beingHookTemplatesDown) ) {
+            $this->assign( 'beingHookTemplatesDown', $this->beingHookTemplatesDown );
+        }
 
     }
 
Index: CRM/Core/Page.php
===================================================================
--- CRM/Core/Page.php    (revision 32975)
+++ CRM/Core/Page.php    (working copy)
@@ -182,6 +182,15 @@
             }
             CRM_Utils_System::civiExit( );
         }
+
+        if ( isset($this->beingHookTemplatesAbove) && !empty($this->beingHookTemplatesAbove) ) {
+            $this->assign( 'beingHookTemplatesAbove', $this->beingHookTemplatesAbove );
+        }
+       
+        if ( isset($this->beingHookTemplatesDown) && !empty($this->beingHookTemplatesDown) ) {
+            $this->assign( 'beingHookTemplatesDown', $this->beingHookTemplatesDown );
+        }
+
         $config = CRM_Core_Config::singleton();
         $content = self::$_template->fetch( 'CRM/common/'. strtolower($config->userFramework) .'.tpl' );
         echo CRM_Utils_System::theme( 'page', $content, true, $this->_print );
Index: templates/CRM/common/drupal.tpl
===================================================================
--- templates/CRM/common/drupal.tpl    (revision 32975)
+++ templates/CRM/common/drupal.tpl    (working copy)
@@ -29,6 +29,12 @@
 
 <div id="crm-container" lang="{$config->lcMessages|truncate:2:"":true}" xml:lang="{$config->lcMessages|truncate:2:"":true}">
 
+{if $beingHookTemplatesAbove}
+  {foreach from=$beingHookTemplatesAbove key=dontCare item=hookTemplateFile}
+     {include file=$hookTemplateFile}
+  {/foreach}
+{/if}
+
 {* we should uncomment below code only when we are experimenting with new css for specific pages and comment css inclusion in civicrm.module*}
 {*if $config->customCSSURL}
     <link rel="stylesheet" href="{$config->customCSSURL}" type="text/css" />
@@ -94,6 +100,12 @@
     {include file=$tplFile}
 {/if}
 
+{if $beingHookTemplatesDown}
+  {foreach from=$beingHookTemplatesDown key=dontCare item=hookTemplateFile}
+     {include file=$hookTemplateFile}
+  {/foreach}
+{/if}
+
 {if ! $urlIsPublic}
 {include file="CRM/common/footer.tpl"}
 {/if}

How do I include templates through my module?
Code: [Select]
// Implemenation of hook_civicrm_pageRun( )
function modulename_civicrm_pageRun( &$page ) {
 // add criteria here when we need to add this template
 $page->beingHookTemplatesAbove[] = realpath(drupal_get_path('module', 'modulename') . '/module_template1.tpl');
}

// Implemenation of hook_civicrm_buildForm()
function modulename_civicrm_buildForm( $formName, &$form ) {
  // add criteria here when we need to add this template
    $form->beingHookTemplatesAbove[] = realpath(drupal_get_path('module', 'modulename') . '/module_template2.tpl');
}


Rajan

Donald Lobo

  • Administrator
  • I’m (like) Lobo ;)
  • *****
  • Posts: 15963
  • Karma: 470
    • CiviCRM site
  • CiviCRM version: 4.2+
  • CMS version: Drupal 7, Joomla 2.5+
  • MySQL version: 5.5.x
  • PHP version: 5.4.x
Re: Including custom templates through hooks
April 06, 2011, 11:00:36 am

hey rajan

overall a good approach and we need to figure out how to allow people to add content w/o modifying template files. So a good step forward

there is a slight issue with this patch for form elements, since u still need beginHookFormElements etc (since the form is created within body.tpl)

ideally would be great if we can figure out how to inject elements in various areas of the form programmatically. I do think that we should look at Drupal 7's render api and formalize the interface between smarty and our php code

lobo
A new CiviCRM Q&A resource needs YOUR help to get started. Visit our StackExchange proposed site, sign up and vote on 5 questions

xavier

  • Forum Godess / God
  • I’m (like) Lobo ;)
  • *****
  • Posts: 4453
  • Karma: 161
    • Tech To The People
  • CiviCRM version: yes probably
  • CMS version: drupal
Re: Including custom templates through hooks
August 02, 2011, 12:38:49 am
Hi,

Nice idea, this it would help developers and make it more robust if you test that beingHookTemplatesAbove is an array (and display an error if it isn't).

Implementation detail: would be better IMO to have zones (ala drupal) instead of hardcode above and below. (so we can add a zone head of footer later for instance).

in the hook you use an api

civicrm_add_template ($zone (one of "top","bottom"),$tpl)

and function civicrm_add_template ($zone,$tpl) {
 $this->extra_template[$zone][]=$tpl;
}

modify the page.php
and in the tpl

{if extra_template['top']}
  {foreach from=$extra_template['top'] key=dontCare item=hookTemplateFile}
     {include file=$hookTemplateFile}
  {/foreach}
{/if}
-Hackathon and data journalism about the European parliament 24-26 jan. Watch out the result

Rajan Mayekar

  • I post frequently
  • ***
  • Posts: 177
  • Karma: 20
    • Rajan's Blogs
Re: Including custom templates through hooks
August 03, 2011, 11:07:20 pm
Hi Xavier,

Yes, most the time it will be really helpful to avoid overriding of template files!

Do you have any thoughts regarding
Quote
ideally would be great if we can figure out how to inject elements in various areas of the form programmatically

( Like drupal alter hooks allow us to inject element any where within a form.. )

xavier

  • Forum Godess / God
  • I’m (like) Lobo ;)
  • *****
  • Posts: 4453
  • Karma: 161
    • Tech To The People
  • CiviCRM version: yes probably
  • CMS version: drupal
Re: Including custom templates through hooks
August 04, 2011, 03:11:24 am
Quote from: Rajan Mayekar on August 03, 2011, 11:07:20 pm
Do you have any thoughts regarding
Quote
ideally would be great if we can figure out how to inject elements in various areas of the form programmatically

Isn't the buildform hook already doing this?

(Otherwise, tend to use jQuery to reshuffle things in the form if needed)

X+
-Hackathon and data journalism about the European parliament 24-26 jan. Watch out the result

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Re: Including custom templates through hooks
August 04, 2011, 05:07:25 pm
I think Rajan is getting at this: Drupal Form API uses one element graph to manage form data and to render markup; by contrast, Civicrms implementation of quickforms only uses the element graph to model data-- the heavy lifting for markup requires smarty (or clever jquery)  and it's all very tightly coupled. Drupal requires fewer steps to add a new form element.

Anyway, its a perfectly valid design tradeoff- - if someone could waive a wand and switch civi to a" component" model that satisfies this complaint, then other people would start complaining about how hard it is to understand the final markup.

Fwiw, I ilike the proposal of standardizing some flexible zones for smarty templates and making those zones programmatically manageable - - it sounds pretty cheap and its better than the status quo.

Donald Lobo

  • Administrator
  • I’m (like) Lobo ;)
  • *****
  • Posts: 15963
  • Karma: 470
    • CiviCRM site
  • CiviCRM version: 4.2+
  • CMS version: Drupal 7, Joomla 2.5+
  • MySQL version: 5.5.x
  • PHP version: 5.4.x
Re: Including custom templates through hooks
August 04, 2011, 07:58:46 pm
Quote from: totten on August 04, 2011, 05:07:25 pm
Fwiw, I ilike the proposal of standardizing some flexible zones for smarty templates and making those zones programmatically manageable - - it sounds pretty cheap and its better than the status quo.

Two things need to happen for the above to be implemented:

1. We need to define an initial set of zones and guidelines on what they mean etc. I dont think ajax injected HTML interferes this, but should we c

2. Modify the existing set of templates to add "zone" support. If we do this, we might also want to "zone-ise" the current code, and hence allow people to hide zones in existing templates

lobo


lobo
A new CiviCRM Q&A resource needs YOUR help to get started. Visit our StackExchange proposed site, sign up and vote on 5 questions

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Re: Including custom templates through hooks
September 05, 2011, 02:03:05 pm
I've got a use-case right now that could benefit from this. (Specifically, I want to manipulate the content that appears at the bottom of civicrm/profile/view for certain profiles.) So I'm looking at this again...

Here are some code-snippets of what I'm thinking of:

== Internal Procederal API ==
CRM_Core_Regions::singleton()->addTemplate('form-footer', 'CRM/foo/bar.tpl');
CRM_Core_Regions::singleton()->addMarkup('form-footer', '<p>HELLO!</p>');
CRM_Core_Regions::singleton()->addCallback('form-footer', 'mymodule_render_something');

== External Procedural API ==
civicrm_api('region','add',array(
    'version' => 3,
    'type' => 'template',
    'file' => 'CRM/foo/bar.tpl',
));
   
== Hook API ==
/**
 * Alter content that is assigned to various regions -- run immediately
 * before final page-rendering
 */
function example_civicrm_regions(&$regions) {
  $regions['form-footer'][] = array(
    'type' => 'template',
    'file' => 'CRM/foo/bar.tpl',
  );
}   
 
== Smarty API ==
{* Render all content that belongs in a given region *}
{crmRegion name="form-footer"}

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Re: Including custom templates through hooks
September 05, 2011, 02:09:46 pm
And to clarify my intent... CRM_Core_Regions would basically represent a global variable that lives for the duration of a single request. When using an API to add new content to a region, the content would only endure for the current page-request -- it would not persist.

One notable implication -- the external procedural API would only be meaningfully when invoked from a PHP context; it would be pointless in REST or Smarty context.

Donald Lobo

  • Administrator
  • I’m (like) Lobo ;)
  • *****
  • Posts: 15963
  • Karma: 470
    • CiviCRM site
  • CiviCRM version: 4.2+
  • CMS version: Drupal 7, Joomla 2.5+
  • MySQL version: 5.5.x
  • PHP version: 5.4.x
Re: Including custom templates through hooks
September 07, 2011, 06:53:26 am

hey tim:

sounds like a good option and better than what we have currently. Maybe we should come up with a defined set of names that we can implement in vaious pages initially.

Some ideas:

page-header -- at top of page, immediatealy after the crm-container div
page-footer  -- at very bottom of page, above the civicrm footer

form-first / form-last / form-middle / form-third / form-two-thirds - first / last / middle  third / two-third element of form
form-LEGEND-first / form-LEGEND-last - if items are grouped together in a legend the same as above

lobo
A new CiviCRM Q&A resource needs YOUR help to get started. Visit our StackExchange proposed site, sign up and vote on 5 questions

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Re: Including custom templates through hooks
September 07, 2011, 12:28:41 pm
Thanks. Makes sense.

 * I like the idea of doing a first/last for each legend/fieldset. Haven't thought about how to make that happen systematically. Maybe we could do a global search-and-replace, converting <fieldset>+<legend> to {crmFieldset}+{crmLegend} -- and then those Smarty functions could handle the regions?
 * Nervous about middle/third/two-thirds
 * Another region with some generic utility might be the "actions" or "buttons" which go at the top/bottom of every form.
 * I'm inclined to go ahead and implement some infrastructure and the page-header/page-footer -- and treat that as an experimental/pre-alpha solution that will give some practical experience before we tap into all the forms.

Donald Lobo

  • Administrator
  • I’m (like) Lobo ;)
  • *****
  • Posts: 15963
  • Karma: 470
    • CiviCRM site
  • CiviCRM version: 4.2+
  • CMS version: Drupal 7, Joomla 2.5+
  • MySQL version: 5.5.x
  • PHP version: 5.4.x
Re: Including custom templates through hooks
September 07, 2011, 01:04:27 pm

yes, you should go ahead and implement it :)

the page/form header/footer can be added automatically to all pages. I think the non-automatic ones, we should do on demand / when needed

not sure how we'll do a double eval, since we'd want to embed in the template strings with smarty tokens in them that smarty will evaluate. I suspect we'll figure something out

lobo
A new CiviCRM Q&A resource needs YOUR help to get started. Visit our StackExchange proposed site, sign up and vote on 5 questions

xavier

  • Forum Godess / God
  • I’m (like) Lobo ;)
  • *****
  • Posts: 4453
  • Karma: 161
    • Tech To The People
  • CiviCRM version: yes probably
  • CMS version: drupal
Re: Including custom templates through hooks
September 07, 2011, 04:13:49 pm
Sounds great. Couple of points.

So now you can inject stuff in the template with:

1) OO PHP (addTemplate)
(not a big fan of a singleton factory +method, IMO should be new methods of the existing template class)
2) API
(what would happen if you call this from {crmAPI or ajax?)
3) hook civicrm_regions
(seems to be almost redundant/big overlap with hook_civicrm_pageRun, and buildTemplate or buildPage seems a better name IMO, bonus point if the hook can change the main template file beside loading templates foe the zone)
4) and Dave's extra.tpl
(that I like more and more)

Anyway, seems to be quite a few too many stones to kill one bird.

As for the 4, it adds the template in a virtual "footer" zone. My personal experience is using it and then jQuery it to $().before or $().after or $().append or ... Basically, creating as many zones as I want as long as I can express it as a dom selector.

Beside the obvious usability and general hackish feeling, this is super convenient, and wondering to what extend no matter how many zones we create, we are still going to miss the specific point in the dom we want to alter.

Another jquery hack I'm using all the time: not only injecting content in various dom "zones", but also alter the existing generated content. I could easily imagine being able to want to alter the generated h1 of the page in the server from a api/hook/whatever instead of doing it on the client side.
How do you see this solution being able to handle that in a v2?

X+

-Hackathon and data journalism about the European parliament 24-26 jan. Watch out the result

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Re: Including custom templates through hooks
September 12, 2011, 10:13:56 am
So I implemented CRM_Core_Region (with Smarty tags and OO API) and held off on the hooks and civircm_api.  (There isn't a strong enough case; there's some inertia in civicrm_api framework; and we've got bigger fish to fry.) The diff file attached provides the implementation as well as a unit-test, example module, and example {crmRegion} inserts.  As one example, an extensible data table can be written as:

Code: [Select]
{crmRegion name="mytable"}
<table>
  <thead>
    <tr>
      {crmRegion name="mytable-header"}
        <th>Foo</th>
        <th>Bar</th>
        <th>Whiz</th>
      {/crmRegion}
    </tr>
  </thead>
  <tbody>
    {foreach var=row ...}
      <tr>
        {crmRegion name="mytable-row"}
          <td>{$row.foo}</td>
          <td>{$row.bar}</td>
          <td>{$row.whiz}</td>
        {/crmRegion}
      </tr>
    {/foreach}
  </tbody>
</table>
{/crmRegion}

To display an extra column, a module could say:

Code: [Select]
CRM_Core_Region::instance('mytable-header')->add(array(
  'markup' => '<th>Extra</th>',
));
CRM_Core_Region::instance('mytable-row')->add(array(
  'template' => 'string:<td>{$row.extra}</td>',
  // Note: This would normally be a file name; "string:" designates an inline template
));

This is an improvement. Note that you get some weak positioning abilities -- by setting a 'weight' property on the new snippet, you can put it before or after the main content and do some other tricks.  (See unit-tests for examples.) But Xavier's point still stands -- you can't position things arbitrarily, unless you (a) write complex, brittle regex or (b) write less-complex, somewhat-brittle jQuery for execution on the client.  To do something more robust, we would need a design in which each <tr>, each <th>, and each <td> could be queried/manipulated via PHP (something like DOM, Form API, or JSF).

What we've basically done here is introduce a rudimentary component model, but we've only "componentized" a few select bits of markup (i.e.  the {crmRegion}s), so we only get programmatic access to few select things. If we wanted, we could follow in the footsteps of many other platforms and slide down the slippery-slope towards a more thorough component model, e.g.

Code: [Select]
      <tr>
        {widget name="mytable-row"}
          {widget}<td>{$row.foo}</td>{/widget}
          {widget}<td>{$row.bar}</td>{/widget}
          {widget}<td>{$row.whiz}</td>{/widget}
        {/widget}
      </tr>

or equivalently

Code: [Select]
      {widget tag="tr" name="mytable-row"}
        {widget tag="td"}{$row.foo}{/widget}
        {widget tag="td"}{$row.bar}{/widget}
        {widget tag="td"}{$row.whiz}{/widget}
      {/widget}

or equivalently

Code: [Select]
      {tr name="mytable-row"}
        {td}{$row.foo}{/td}
        {td}{$row.bar}{/td}
        {td}{$row.whiz}{/td}
      {/tr}

Any instance of {crmRegion}/{widget}/{tr}/{td} would be a component that could be located, extended, or overriden via PHP -- building on the approach of {crmRegion}. This, of course, raises a few questions:

 * What does performance look like when half the tags get reprocessed on the off-chance that some component has opted to extend them? Aren't we going to add an extra 150ms to every page request?
 * What should all those component-types be? Do they have nuances (e.g. {tbody} behaves like {ol} but not like {p})?) Does it really make sense to invent a new list of component types when we've already got HTML/DOM?
 * How are we going to transition all the markup in all the .tpl files?
 * What does debugging look like when any tag is subject to runtime manipulation?
 * How does a developer know that his changes are upgrade-safe?

To address these questions, I'm thinking about a design based on "compile-time selectors". It works like this:

 1. The downstream developer makes a delcaration that he wants programmatic access to certain parts of a template, e.g. (pseudocode)
Code: [Select]
    function example_civicrm_injection() {
      return array(
        array(
          'file' => 'CRM/Contact/Form/Selector.tpl',
          'selector' => 'table td:nth-child(3)', // note: CSS selector expression
          'name' => 'my-injection-point',
          'append' => 'string:<td>{$row.extra}</td>',
        )
      );
    }
2. As part of the Smarty compilation process for Selector.tpl, we parse the .tpl file as an HTML document, search DOM for elements which match the selector, and convert them into extensible components. (http://simplehtmldom.sourceforge.net/ does a lot of the hard parsing/searching work)
 3. Optionally, we provide a validation utility which downstream developers can run as part of an upgrade/migration. This utility iterates through all the declared templates/selectors, compiles them, and reports invalid selectors. Unlike standard jQuery, this validation only requires compiling the .tpl files -- you don't need to go through the harder problem of producing final HTML (which requires loading sample data and running business logic). Bonus points can be awarded if the validation utility tracks compares the content of the selector in different revisions of CiviCRM.

In theory, this comes with a few caveats -- the developer has to declare the injection points somewhere separate from their PHP business logic, and it only works with .tpl files that are well-formed XML/HTML. However, it can be implemented generically; it provides a measure of maintainability/verifiability; and it makes the overhead proportionate to "#actual extension points" rather than "#potential extension points".

Anyway, that's an idea.

xavier

  • Forum Godess / God
  • I’m (like) Lobo ;)
  • *****
  • Posts: 4453
  • Karma: 161
    • Tech To The People
  • CiviCRM version: yes probably
  • CMS version: drupal
Re: Including custom templates through hooks
September 12, 2011, 10:20:26 am
Quote from: totten on September 12, 2011, 10:13:56 am
Code: [Select]
CRM_Core_Region::instance('mytable-header')->add(array(
  'markup' => '<th>Extra</th>',
));
CRM_Core_Region::instance('mytable-row')->add(array(
  'template' => 'string:<td>{$row.extra}</td>',
  // Note: This would normally be a file name; "string:" designates an inline template
));

So, assuming mytable-header is the same for every table, you'd be into the buildForm or Page hook and based on the context decides to inject CRM_Core_Regions ?

X+
-Hackathon and data journalism about the European parliament 24-26 jan. Watch out the result

Pages: [1] 2
  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion (Moderator: Donald Lobo) »
  • Including custom templates through hooks

This forum was archived on 2017-11-26.