Skip to content

Custom Control

Jorge Castro edited this page Feb 4, 2024 · 7 revisions

There are several ways to create a new control (tag). BladeOne does not support Laravel x-controls but it has other ways to do the same operation.

What is a control?

For example, let's say we have the tag @dump, which dumps the values in the screen. In the same way, we have dozens of tags available, but what if we want to create our tag? That is a custom control.

lifecycle

The lifecycle of BladeOne is the next one:

  1. the client calls a webpage
  2. BladeOne generates a compiled code (if it doesn't exist), this compiled code is PHP code. (it is done usually once)
  3. The compiled code is executed (runtime, it is done every time the code is used)
  4. The result is displayed.

compile vs runtime vs component

There are 3 ways to create a control, compile time, runtime, and component.

Component is the easy way without code and only with templates: template component In this document, we will examples of the other two ways, compile and runtime. Compile is a code that returns the next text:

<?PHP echo "the sum is ".sum(20,30); ?>

Runtime is a code that returns a result that is evaluated at runtime (every time the control is displayed)

the sum is 50 

Compile control usually is faster than runtime control but commonly the difference is not noticeable. A custom control could even connect to the database, while it could break the separation of the layer (programming pattern) but sometimes it could be worth to access to the database only when the control is displayed.

Creating a compiled control.

A compiled code is a method that creates a PHP file. It gives the best performance because it generates a PHP code but it lacks flexibility and it requires to think the code generated must be a PHP code.

Extending the class:

It is possible to add a new compile tag creating a new method inside the class. This method must start with the name "compile", and optionally could contain an expression. You can find more information here: extending the class

This code returns a PHP code, so this method is only executed at compile time. In the code

class MyClass extends  BladeOne {
   public function compileNewtag($expression) { // @newtag(...)
        return BladeOne::$instance->wrapPHP("operation$expression",false,false); // it will return the next code <?PHP echo operation(....); ?>
   }
}

In the template:

  @newtag("hello","world")

Adding a method without extending the class.

If you don't want to extend the class, then you can add a new method using the next way:

// blade is an instance of BladeOne.
BladeOne::$instance->addMethod('compile','newtag',function($args) {
      return BladeOne::$instance->wrapPHP("operation$expression",false,false); 
});

Creating a control with named arguments

You can separate every argument using the method getArgs: Code:

    public function compileHelloNamed($expression)
    {
        $args = $this->getArgs($expression); // args separates the arguments by name
        $name=$args['name']?? '--empty--'; // if the argument name is missing then it uses a default value.
        $surname=$args['surname']?? '--empty--'; // you can also use array_merge, conditions, etc. to set default values        
        return "<?php echo 'Hello '.$name. ' '.$surname; ?>";
    }

Template:

 helloNamed(name="hello" surname="world")

Creating a runtime control

Sometimes you want something more advanced. In this case, you can create a control at runtime.

This method is called every time it is visualized, instead, the compile method is called only when the control is compiled (usually once). This method must start with the name "runtime", and the second letter must be in uppercase.

In the code:

BladeOne::$instance->addMethod('runtime','newtag',function(array $args) { // @newtag(a1="20" a2="30")
   return "method one ".$args['a1'].','.$args['a2'];
});
// or
class MyClass extends  BladeOne {
   public function runtimeNewtag(array $args) { 
        return "method one ".$args['a1'].','.$args['a2'];
   }
}

In the template:

  @newtag(a1="20" a2="30")

Note: The template code for runtime control is written differently. The runtime version allows named arguments and they mustn't be separated by a comma. runtime: @newtag(a1="20" a2="30") compile: @newtag("20","30") or compile with arguments @newtag(a1="20" a2="30") Note: if the argument hasn't a name @newtag("hello") instead of @newtag(name="hello"), then the value could be obtained numerically($args[0])

Complete runtime code

There are different methods to help us to interact with custom controls (runtime)

  • $blade->addControlStackChild('controlname',$arguments) it stores the name and value of the control in a stack. If you want to get the values of control, then use it, otherwise you don't need to use it in all controls. It also converts the new child to the current parent.
  • $blade->addControlStackSibling('controlname',$arguments) its equal to addControlStackChild but it adds the current control as a sibling of the latest control
  • $blade->closeControlStack() it gets the last control added to the stack and this method removes it from the end of the stack.
  • $blade->closeControlStack() it gets the last parent control added to the stack and this method removes it from the end of the stack.
  • $blade->lastControlStack() it gets the last control added to the stack without removing.
  • $blade->parentControlStack() it gets the current parent control
  • $blade->clearControlStack() it clears all the controls in the stack.

For example, let's say we want to create a table with a row (or rows). The table contains the data while the row displays it. Template: (where countries is an array with names, and alias is the alias used in the row (the alias avoids to conflict with nested objects)

@table(values=$countries alias="alias")
row:
@row()
row2:
@row2()
@endtable

Code:

// it clears the previous methods created in different tests
BladeOne::$instance->clearMethods();
BladeOne::$instance->addMethod('runtime', 'table', function($args) {
	// you could use array merge to set a default value, or use conditions, ternary operators, etc.
	$args = array_merge(['alias' => 'alias'], $args); 
	// we store the current control in the stack, and we turn @table as the current parent
	BladeOne::$instance->addControlStackChild('table', $args); 
	return '<ul>';
});
BladeOne::$instance->addMethod('runtime', 'endtable', function($args) {
	// it gets the last control, parent or child
	$latest=BladeOne::$instance->lastControlStack();
	// optionally you can add a validator and validate if the previous tag is the correnct
	if($latest['name']!=='table') { 
		// it shows an error and throw an exception
		BladeOne::$instance->showError('@endtable', 'Missing @table',true,true);
	}
	BladeOne::$instance->closeControlStackParent(); // it closes the parent (@table)
	return '</ul>';
});
BladeOne::$instance->addMethod('runtime', 'row', function() {
	// getting the values of the parent control (@table) using the stack
	// note: we don't need to add a child everytime a new control is added, its optional
	$parent = BladeOne::$instance->parentControlStack()['args']; 
	$result = '';
	foreach ($parent['values'] as $v) {
		$result .= BladeOne::$instance->runChild('auto.test2_control', [$parent['alias'] => $v]);
	}
	return $result;
});
BladeOne::$instance->addMethod('runtime', 'row2', function() {
	// getting the values of the parent control (@table) using the stack
	// note: we don't need to add a child everytime a new control is added, its optional
	$parent = BladeOne::$instance->parentControlStack()['args']; 
	$result = '';
	foreach ($parent['values'] as $v) {
		$result .= "<li>$v</li>\n";
	}
	return $result;
});

result:

<ul>
row:
   <li>Chile</li>
   <li>Argentina</li>
   <li>Peru</li>
row2:
   <li>Chile</li>
   <li>Argentina</li>
   <li>Peru</li>

</ul>
Clone this wiki locally