In this help we will go through step-by-step instructions for building a new model. Rather than build a completely new model we will show how the faq model was built. GenHelm provides the source code for all of the supplied models so if you are logged into GenHelm you can review the components that we describe below. Note that, in order to protect intellectual property, some of the specifications for GenHelm internal components are not supplied and some source code has be obfuscated.

Here we see a sample faq page:

Sample FAQ page

Most websites have one or more FAQ pages. The prevalence of this type of component makes it a good candidate for incorporating into a model.

Identifying Model Candidates

Repeatable Pattern

Often, déjà vu is the "trigger" for deciding to build a new model. If you find yourself developing components that follow a similar pattern over and over again this is a good indication that you should consider creating a new model to formalize this pattern. Another strong indicator is when you decide to clone a class or other component in order to build a new component.

Limited Expertise

If there are components being developed within your organization that require expertise that is limited to one or two individuals you should consider "bundling" this expertise in the form of a GenHelm model. This would enable non-experts to build these components in a standardized way that takes advantage of the knowledge and experience that the experts have incorporated into the model.

Error Prone Patterns

If you find that errors frequently occur in a certain class of component it might be possible to reduce the likelihood of such errors by generating these components rather than writing them by hand.

Tedious, Mundane or Time Consuming Development Processes

If you find yourself developing components that take a long time or involve a lot of "mechanical" coding, these components are often good candidates for encapsulating into a new GenHelm model.

Refining the Generation Target

We use the term "prototype" to refer to the component(s) that you want to be able to generate. Before you embark on creating a new model a very important step involves perfecting the prototype.  If your prototype is poorly written, bug infested or inefficient, modelling a generator after this will only serve to propagate the prototype's shortcomings every time a new component is generated. GenHelm models seek to proliferate excellent, efficient, bug free code, therefore, never start to develop your model until your prototype reaches a state of "nirvana".

In addition to qualitative factors, also ponder the robustness of your prototype by considering these questions:

  • Does it incorporate all the features you think you will need?
  • Is it flexible in how it is used?
  • Is it nimble to change?
  • Can multiple components coexist?
  • Does it inherit common properties of behavior?

This last item is particularly important when generating classes. It only makes sense to create a model to generate a certain type of php class when all of the classes that will be generated share common characteristics. Usually, this is an indicator that all of these similar classes should share the same base class so that common behavior can be inherited rather than replicated. By shifting functionality into base classes this usually simplifies the subclasses which are your generation candidates. 

Prototypes used to establish your model requirements should cover as many anticipated features as possible since these features will tend to dictate the parameters that will be required by your model. Sometimes you may need to create several different prototypes in order to discern all of the capabilities that your model will need to handle.

When generating components that need to run within the context of an HTML page be sure to consider the fact that your component should not interfere with other components or even itself. For example, let's say you are generating a "menu gadget". If you hardcode element ID tags your component will work fine on web pages that only have one menu gadget but may break if someone decides to put two such gadgets on the same page (due to having duplicate identifiers). Often ids are derived from the component name to avoid such conflicts.

Creating The FAQ Page Prototype

Before we embark on creating the FAQ model we need to focus on what the model will be generating (our prototype). Since FAQ pages have a lot in common with other types of web pages it makes sense that the FAQ prototype will inherit much of its functionality. In fact, all of the page-oriented models inherit from a framework class named html_page_base_class. By inheriting from this class we achieve our goal of simplifying the code that is needed to implement an FAQ page at runtime and, in so doing, we simplify the model that we will be creating.

Here we see that all of the page oriented runtime components are located within the private_data/system/classes/page_component folder:

Page component runtime classes

All of these classes inherit html_page_base_class (or a subclass of this class). One of the more useful methods of this base class is add_content. This method is passed information that you want to render into your page. One of the features of this method is that it parses any dollar functions automatically.

Here is an example showing how a subclass of html_page_base_class could call this method:

$this->add_content('this will be rendered first');
$this->add_content('Today is $date(,,yyyy-mm-dd)');

Getting back to our faq model, here we see the structure of the HTML that needs to be generated to implement a typical faq page:

<div>
 <details>
   <summary>First question?</summary>
   First answer.
 </details>
 <details>
  <summary>Second Question?</summary>
  Second answer.</p>
 </details>
</div>

The first question we should ask ourselves is "Does this prototype do everything we will likely need our model to generate?"

If the answer is "No", then we should extend and perfect our prototype until it includes all of the features we might need. Let's go over some of these.

  1. It would be nice to allow our questions to be numbered as an optional feature.
  2. What if some of the answers are quite long? Mightn't we want to divide these into paragraphs?
  3. What if we want to style our questions and/or answers? Having CSS class properties for these would make this easier.
  4. What if we want to "reveal" the answers to the questions automatically rather than forcing the user to click on each question in order to see the answer?
  5. We may want to be able to change the css class associated with the outer div tag.
  6. If we have a lot of questions, we may want to organize these into groups? Adding optional H2 tags would facilitate this.

Now that we have considered these likely requirements we should create a new enhanced prototype that incudes these extended features.

<div id="faqs_questions" class="faq_questions">
 <details open="open" class="faq_details">
   <summary class="faq_summary">1. First question?</summary>
   <p>First answer paragraph 1.</p>
   <p>First answer paragraph 2.</p>
 </details>
 <h2 class="faq_header">Section Heading</h2>
 <details class="faq_details">
  <summary class="faq_summary">2. Second Question?</summary>
  <p>Second answer paragraph 1</p>
 </details>
</div>

Now that we have decided on the HTML that the FAQ prototype will be rendering you might think that it is time to proceed to design our model. In fact, it is still too early for this step. The reason is that our model won't be generating HTML like the prototype HTML above.  Instead, we are going to create a GenHelm framework runtime class that is capable of rendering such HTML.

You may be wondering "Why not just have the model generate the target HTML?"

The answer is that we strive to increase reusability. If the model generates the target HTML for faq pages it could not be leveraged very easily if you ever need to generate an FAQ page on-the-fly. For example, let's say you are developing courseware and as part of this you want the system to ask students a list of questions that are randomly selected from a large set of potential questions. Such a page would need a class that it could call, passing it the questions and answers, and having it render the page. Since having an FAQ page generator as part of the common runtime is beneficial from a reusability standpoint, it makes sense to create this as a component first, then the faq model can layer on top of this component.

To build this runtime component you would analyze the HTML that needs to be rendered at runtime and decide what methods are going to be needed to create this code. We will start by showing the completed code and then we will break it down. Here is the html_page_faq class that will generate the HTML shown above:

require_once CLASS_PATH.'html_page_base_class.php';
class html_page_faq extends html_page_base_class {
protected $div_wrapper_properties;
protected $add_question_numbers;
private $current_question_number = 0;
function get_add_question_numbers() {
  if (!isset($this->add_question_numbers)) {
    return false;
  }
  return $this->add_question_numbers;
}
function set_div_wrapper_properties($value) {
  $this->div_wrapper_properties = $value;
}
function set_add_question_numbers($value) {
  $this->add_question_numbers = \system\variable::cast($value,'boolean',false,false);
}
function add_question($question,$answer,$open=false) {
  if (is_array($answer)) {
      $detail = ' <p>'.implode('</p><p>',$answer).'</p>';
  }
  else {
      $detail = ' <p>'.$answer.'</p>';
  }
  $number = $this->add_question_numbers ? ++$this->current_question_number.'. ' : '';
  $open_tag = $open ? ' open="open"' : '';
  $code = '<details'.$open_tag.' class="faq_details">'.PHP_EOL.' <summary class="faq_summary">'.
    $number.$question.'</summary>'.PHP_EOL.$detail.PHP_EOL.'</details>';
  $this->add_content($code);
}
function div_start() {
  if (empty($this->div_wrapper_properties)) {
    $properties = ' id="'.$this->get_pageid('!/').'_questions" class="faq_questions"';
  }
  else {
    $properties = ' '.$this->div_wrapper_properties;
  }
  $this->add_content('<div'.$properties.'>');
}
function add_heading($heading) {
    $this->add_content('<h2 class="faq_header">'.$heading.'</h2>');
}
}

Our html_page_faq class was created while logged into the system site so that all sites can use this class. Since this class is in a subfolder our initial stow command to create this class would be "stow page_component/html_page_faq". Let's review all of the components of this class.

Inherited Class

To inherit a class we simply need to enter the base class into the Extends Class property.  This will also cause the require_once statement to be generated automatically.

Enter base class in the Extends Class field

Get and Set Methods

The get and set methods shown were generated automatically based on setting properties of the php_class model used to generate this class. Here we see that we requested a set method for div_wrapper_properties and a get and set method for add_question_numbers:

FAQ Runtime class properties

We defined a string property in case callers want to override the outer div properties. This is more flexible than just allowing the CSS class to be overridden.

We also added a boolean property to allow the callers to indicate that the questions are to be numbered. We have used Coerce as the set method generation setting to automatically convert passed values to a boolean.

Finally, we define a private property to keep track of which question number we are rendering.

Rendering the div Tag

We see that the code we want to render starts with a div tag, so let's define a method to generate this div. You want to try to make your methods as flexible as possible while keeping them simple to use for common cases. As an example of this principle, we are going to allow the users of the html_page_faq class to set the properties of this div but if the caller does not set any properties we will define suitable default properties. By default we will generate the properties:

id="[page_name]_questions" class="faq_questions"

function div_start() {
  if (empty($this->div_wrapper_properties)) {
    $properties = 'id="'.$this->get_pageid('!/').'_questions" class="faq_questions"';
  }
  else {
    $properties = $this->div_wrapper_properties;
  }
  $this->add_content('<div '.$properties.'>');
}

Notice that we call the inherited method add_content to render the div tag.

Rendering the details Tag

Each question and answer sequence requires a details tag, a summary tag and paragraph tags for each answer paragraph. Next we will create a method to generate each answer. Once again we try to consider what users of this class might want to do. We decide to add two "features" to this method:

We want to give the users of this class the option to number the questions.

We want to allow answers to be revealed by default (by setting the open property of the details tag).

Here we see a function that can generate the required tags:

function add_question($question,$answer,$open=false) {
if (is_array($answer)) {
    $detail = ' <p>'.implode('</p><p>',$answer).'</p>';
}
else {
    $detail = ' <p>'.$answer.'</p>';
}
$number = $this->add_question_numbers ? ++$this->current_question_number.'. ' : '';
$open_tag = $open ? ' open="open"' : '';
$code = '<details'.$open_tag.' class="faq_details">'.PHP_EOL.' <summary class="faq_summary">'.
  $number.$question.'</summary>'.PHP_EOL.$detail.PHP_EOL.'</details>';
$this->add_content($code);
}

As you can see, the caller passes in the question and the answer and optionally can specify whether to open the details tag. To make the interface simple to use we will allow the answer to be passed as a string when only one paragraph is needed and we allow an array of answer strings to support multi-paragraph answers. Since the decision to show question numbers applies to all of the questions, we don't pass this as a parameter but rather make this a property of the class.

Once again we call the add_content method to render the generated HTML so that any dollar functions within the questions or answers are parsed for us automatically.

Dividing Questions into Groupings

For pages that have a lot of questions and answers it might make sense to group the questions according to the type or nature of the question. Therefore, let's add a third method to our faq class to render an h2 tag.

function add_heading($heading) {
    $this->add_content('<h2 class="faq_header">'.$heading.'</h2>');
}

The only thing left that needs to be rendered is the ending div tag. As it happens, the html_page_base_class has a function named div_end that can generate this for us so we don't need to write any custom code for this.

That's all the coding we need to render our faq pages thanks to the fact that we have inherited most of the functionality from html_page_base_class.  At this point it would be prudent to create a simple custom page to test this class. This way we can confirm that the html_page_faq class does what we need before we start working on our model.

Simple Custom Page

Here we see an example of a simple page that was implemented using the custom model to test our html_page_faq class:

Custom code used to test html_page_faq

As with all custom pages, we need to supply a function body with a Section Id of generate. Most of this code is what you would expect. That is, it is instantiating the html_page_faq class and calling methods of this class to set the FAQ information. There are a couple of statements that we will discuss in detail.

Instantiating Objects

We use the site object's create_object method to instantiate our html_page_faq class. One of the benefits of using this method is that we don't need to require_once the class code since this is handled for us automatically. Also, the create_object method supports site inheritance. That is, if the class does not exist within the current site the method will look in the inherited (industry) site and if it is not found there it will look in the system pseudo site.

Here we see the signature of the create object method.

Function &create_object($std_class,$industry='',$persist=false,$subdirectory='',$soft_failure=false)

Here we describe the parameters to this method:

  1. The class to be instantiated.
  2. To search a specific site you can pass the site directory in parameter 2.
  3. Pass true if you want the object (values) to be preserved across page requests. This only applies to single object classes.
  4. If the class is within a subfolder of the classes folder, enter the path here.
  5. If you don't want to raise a runtime error if the class is not found pass true in this parameter.

The generate Method

One of the methods that is inherited from html_page_base is named generate. This assembles all of the code that created by calls to the add_content method and returns this. We return this as part of the custom page's generate method to generate the FAQ content via our custom page.

The Rendered Page

Here we see an example of the page that is rendered by our custom page. This is just using the default browser styling.

Custom page which calls faq page.

What's Next

In this help we have concentrated on showing what our model will be generating and we stressed the importance of making sure your prototype is well thought out. The code our model will be generating is very similar to what we used in the custom page. The main difference is that the code we generate will actually be included as a .inc file directly by html_page_faq and the framework will be instantiating this class for us. Here we show the actual code that will be generated to implement the sample page above:

$this->set_div_wrapper_properties('class="override"');
$this->set_add_question_numbers(true);
$this->div_start();
$this->add_heading('Section Heading 1');
$this->add_question('Question 1',
		array('Answer 1 Paragraph 1', 'Answer 1 Paragraph 2', 'Answer 1 Paragraph 3'),
		true);
$this->add_question('Question 2','Answer 2 Single Paragraph');
$this->add_heading('Section Heading 2');
$this->add_question('First question of Section 2',
		array('Answer to first question in section 2.','Another paragraph.'));
$this->div_end();

The next step will be to build our model to generate this code.

Specification Used by the Model Model
🡇
Specification Used by the Model Model