【问题标题】:Mustache.php : Idiomatic ways to template select dropdownsMustache.php :模板选择下拉菜单的惯用方法
【发布时间】:2026-01-04 22:10:02
【问题描述】:

所以我在思考如何使用 Mustache.php 灵活处理一些复杂的 html 案例的最佳惯用方法时遇到了一些问题

第一个是预先选择的选择下拉菜单,例如

<select>
  <option value=''></option>
  <option value='bob'>Bob Williams</option>
  <option value='james' selected>James Smith</option>
</select>

我有办法解决这个问题,但我的方式似乎很不灵活:

  • 在php中取一个数组,
  • 将其重新格式化为具有 3 个元素的多维数组;值、显示、选择(布尔)
  • 将其传递给模板,在该模板中循环输出选项、值和选定项

是否有一种很棒的方法可以使用部分或匿名函数或方法或我缺少的 mustache.php 的其他一些功能来制作预选下拉列表?

编辑:将此问题简化为单独的部分,以尽量提高清晰度。

【问题讨论】:

    标签: mustache templating


    【解决方案1】:

    在 Mustache 中执行此操作的惯用方式是创建一个视图(或 ViewModel),而不是传入数据散列:

    <?php
    
    class Dropdown
    {
      public  $name;
      public  $value;
      private $options;
    
      public function __construct($name, array $options, $value)
      {
        $this->name    = $name;
        $this->options = $options;
        $this->value   = $value;
      }
    
      public function options()
      {
        $value = $this->value;
    
        return array_map(function($k, $v) use ($value) {
          return array(
            'value'    => $k,
            'display'  => $v,
            'selected' => ($value === $k),
          )
        }, array_keys($this->options), $this->options);
      }
    }
    

    然后您可以将其与 dropdown 部分...

    <select name="{{ name }}">
      {{# options }}
        <option value="{{ value }}"{{# selected }} selected{{/ selected }}>
          {{ display }}
        </option>
      {{/ options }}
    </select>
    

    您可以像这样在模板中使用:

    {{# state }}
      <label for="{{ name }}">State</label>
      {{> dropdown }}
    {{/ state }}
    
    {{# country }}
      <label for="{{ name }}">Country</label>
      {{> dropdown }}
    {{/ country }}
    

    并渲染它:

    <?php
    
    $data = array(
      'state'   => new Dropdown('state',   $someListOfStates,    'CA'),
      'country' => new Dropdown('country', $someListOfCountries, 'USA'),
    );
    
    $template->render($data);
    

    ...但你可以做得更好:)

    有了这个:

    <?php
    
    class StateDropdown extends Dropdown
    {
      static $states = array(...);
    
      public function __construct($value, $name = 'state')
      {
        parent::__construct($name, self::$states, $value);
      }
    }
    

    还有这个:

    <?php
    
    class CountryDropdown extends Dropdown
    {
      static $countries = array(...);
    
      public function __construct($value, $name = 'country')
      {
        parent::__construct($name, self::$countries, $value);
      }
    }
    

    其中之一:

    <?php
    
    class Address
    {
      public $street;
      public $city;
      public $state;
      public $zip;
      public $country;
    
      public function __construct($street, $city, $state, $zip, $country, $name = 'address')
      {
        $this->street  = $street;
        $this->city    = $city;
        $this->state   = new StateDropdown($state, sprintf('%s[state]', $name));
        $this->zip     = $zip;
        $this->country = new CountryDropdown($country, sprintf('%s[country]', $name));
      }
    }
    

    输入一个新的address partial:

    <label for="{{ name }}[street]">Street</label>
    <input type="text" name="{{ name }}[street]" value="{{ street }}">
    
    <label for="{{ name }}[city]">City</label>
    <input type="text" name="{{ name }}[city]" value="{{ city }}">
    
    {{# state }}
      <label for="{{ name }}">State</label>
      {{> dropdown }}
    {{/ state }}
    
    <label for="{{ name }}[zip]">Postal code</label>
    <input type="text" name="{{ name }}[zip]" value="{{ zip }}">
    
    {{# country }}
      <label for="{{ name }}">Country</label>
      {{> dropdown }}
    {{/ country }}
    

    更新您的主模板:

    <h2>Shipping Address</h2>
    {{# shippingAddress }}
      {{> address }}
    {{/ shippingAddress }}
    
    <h2>Billing Address</h2>
    {{# billingAddress }}
      {{> address }}
    {{/ billingAddress }}
    

    走吧!

    <?php
    
    $data = array(
      'shippingAddress' => new Address($shipStreet, $shipCity, $shipState, $shipZip, $shipCountry, 'shipping'),
      'billingAddress'  => new Address($billStreet, $billCity, $billState, $billZip, $billCountry, 'billing'),
    };
    
    $template->render($data);
    

    现在,您有了模块化、可重用、易于测试、可扩展的代码和部分代码。

    请注意,我们创建的类是“Views”或“ViewModels”。它们不是您的域模型对象...它们不关心持久性或验证,它们只关心为您的模板准备值。如果你也在使用模型,那就更容易了,因为像我们的地址类这样的东西可以包装你的地址模型,并直接从模型中获取它需要的值,而不是要求你将一堆东西传递给构造函数.

    小胡子禅

    如果您采用这种方法得出其合乎逻辑的结论,您最终会在您的应用中为每个操作/模板对创建一个*视图或 ViewModel 类——视图可以在内部委托给子视图和部分视图,就像我们对我们的地址视图中的下拉列表,但您将有一个一流的视图或视图模型负责呈现每个操作。

    意思是(在 MVC/MVVM 世界中),您的 Controller 操作将执行所需的任何“操作”,然后创建负责填充模板的 View 或 ViewModel 类,将几个域模型对象交给它,然后在模板上调用渲染。控制器不会准备任何数据,因为这是视图层的责任。它会简单地将几个模型对象交给它。

    现在你所有的“渲染”逻辑都整齐地封装在视图层中,你所有的标记都整齐地封装在你的模板文件中,你的模型摆脱了丑陋的格式化业务,你的控制器就像它应该的那样漂亮和轻巧:)

    【讨论】:

    • 嗯,我得花点时间来理解这一点。我的部分问题可能是我正在使用参数化的 pdo 对象来获取结果集,当需要修改模板的数据时,这最终会变得有些不灵活,必须在结果集中的数据转储或一些其他不受欢迎的选择。
    • 这是另一个使用带有 Mustache 的“真实 ViewModel”的示例:gist.github.com/61161639d8be82a75b5e
    【解决方案2】:

    bobthecow 是一个很好的起点,但代码中存在一些错误。

    我必须更改下拉类以支持 kohana 中的多选下拉菜单,并且 array_map 要求您将相同数量的变量传递给函数。

    下拉类:

    class Tiaa_Dropdown
    {
      public  $name;
      public  $value;
      private $options;
    
      public function __construct($name, array $options, $value)
      {
        $this->nameAttr    = $name;
        $this->options = $options;
        $this->value   = $value;
      }
    
      public function things()
      {
        $value = $this->value;
        $array_keys = array_keys($this->options);
        $array_values = array_values($this->options);
        return array_map(function($k, $v) use ($value) {
    
      if(is_array($value)) {
        $selected = (in_array($k, $value) ? 1 : 0);
      } else {
        $selected = ($value == $k);
      }
    
         return array(
            'value'    => $k,
            'display'  => $v,
            'selected' =>  $selected
          );
        }, $array_keys, $array_values);
      }
    } // End
    

    希望这对某人有所帮助。

    【讨论】:

      最近更新 更多