Skip to content

Commit b52c2c0

Browse files
committed
Merge branch 'arbitrary_attr'
2 parents e9bf1cb + 9328cee commit b52c2c0

File tree

9 files changed

+172
-66
lines changed

9 files changed

+172
-66
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 1.4.0
2+
3+
## Breaking changes
4+
5+
- Form attributes is now pulled directly from `$options`, `attr`
6+
option is still present but deprecated.
7+
18
# 1.3.0
29

310
## Features

docs/options.php

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,20 @@
1616
<h1>PHP Forms <small>Options</small></h1>
1717
<?php include('menu.php') ?>
1818

19-
<h2>Form options</h2>
20-
<dl>
21-
<dt>method</dt>
19+
<h2 id="form-options">Form options</h2>
20+
<dl class="doc-table">
21+
<dt><code>method</code> <span class="label type-string">string</span></dt>
2222
<dd>Form method (default: post)</dd>
23-
<dt>method_field_name</dt>
23+
<dt><code>method_field_name</code> <span class="label type-string">string</span></dt>
2424
<dd>Name of the special method field when using methods other than <tt>GET</tt> and <tt>POST</tt></dd>
25-
<dt>action</dt>
25+
<dt><code>action</code> <span class="label type-string">string</span></dt>
2626
<dd>Form action (default: "")</dd>
27-
<dt>enctype</dt>
28-
<dd>Form enctype</dd>
29-
<dt>layout</dt>
27+
<dt><code>layout</code> <span class="label type-string">string</span></dt>
3028
<dd>Form layout ("plain", "table", "bootstrap" or an instance of <code>FormLayout</code>)</dd>
31-
<dt>prefix</dt>
29+
<dt><code>prefix</code> <span class="label label-default">mixed</span></dt>
3230
<dd>Use custom prefix when generating names and ID</dd>
33-
<dt>attr</dt>
34-
<dd>Array with arbitrary attributes to pass directly to form.</dd>
3531
<dt>Other</dt>
36-
<dd>Form also accepts <code>style</code>, <code>class</code> and <code>data</code> which is just passed directly to the form.</dd>
32+
<dd>All other options are passed directly to the form as attributes. Use it to specify attributes such as <code>name</code>, <code>class</code>, etc.</dd>
3733
</dl>
3834

3935
<h3>Default options</h3>

docs/style.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,11 @@ h4[id]:hover .link {
107107
.doc-table .type-html {
108108
background-color: #007;
109109
}
110+
111+
.doc-table .type-callable {
112+
background-color: #707;
113+
}
114+
115+
.doc-table .type-array {
116+
background-color: #770;
117+
}

docs/usage.php

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,57 @@ static protected function default_options(){
2727
<h1>PHP Forms <small>Usage</small></h1>
2828
<?php include('menu.php') ?>
2929

30-
<h2 id="generic">Generic usage</h2>
30+
<h2 id="unbound">Simple form (unbound)</h2>
31+
<?=code('Form::create(string $id, callable $callback, [array $options])', 'php')?>
32+
<dl class="doc-table">
33+
<dt><code>$id</code> <span class="label type-string">string</span></dt>
34+
<dd>Form id (prefix).</dd>
35+
<dt><code>$callback</code> <span class="label type-callable">callable</span></dt>
36+
<dd>Build callback.</dd>
37+
<dt><code>$options</code> <span class="label type-array">array</span></dt>
38+
<dd>Optional options and form attributes, see <a href="options.php#form-options">Form Options</a></dd>
39+
</dl>
40+
<h3>Example</h3>
41+
<div class="row">
42+
<div class="col-sm-6"><?php display('usage/unbound.php'); ?></div>
43+
<div class="col-sm-6"><?php include('usage/unbound.php'); ?></div>
44+
</div>
45+
46+
<h2 id="array">Array binding</h2>
47+
<?=code('Form::from_array(string $id, array $array, callable $callback, [array $options])', 'php')?>
48+
<dl class="doc-table">
49+
<dt><code>$id</code> <span class="label type-string">string</span></dt>
50+
<dd>Form id (prefix).</dd>
51+
<dt><code>$array</code> <span class="label type-array">array</span></dt>
52+
<dd>Array to bind data from.</dd>
53+
<dt><code>$callback</code> <span class="label type-callable">callable</span></dt>
54+
<dd>Build callback.</dd>
55+
<dt><code>$options</code> <span class="label type-array">array</span></dt>
56+
<dd>Optional options and form attributes, see <a href="options.php#form-options">Form Options</a></dd>
57+
</dl>
58+
<h3>Example</h3>
59+
<div class="row">
60+
<div class="col-sm-6"><?php display('usage/array.php'); ?></div>
61+
<div class="col-sm-6"><?php include('usage/array.php'); ?></div>
62+
</div>
63+
64+
<h2 id="object">Object binding</h2>
65+
<?=code('Form::from_object(object $obj, callable $callback, [array $options])', 'php')?>
66+
<dl class="doc-table">
67+
<dt><code>$object</code> <span class="label type-object">object</span></dt>
68+
<dd>Object to bind data from.</dd>
69+
<dt><code>$callback</code> <span class="label type-callable">callable</span></dt>
70+
<dd>Build callback.</dd>
71+
<dt><code>$options</code> <span class="label type-array">array</span></dt>
72+
<dd>Optional options and form attributes, see <a href="options.php#form-options">Form Options</a></dd>
73+
</dl>
74+
<h3>Example</h3>
75+
<div class="row">
76+
<div class="col-sm-6"><?php display('usage/object.php'); ?></div>
77+
<div class="col-sm-6"><?php include('usage/array.php'); ?></div>
78+
</div>
79+
80+
<h2 id="generic">Fields</h2>
3181
<p>The general prototype is:</p>
3282
<?=code('xyz_field($key, $label=null, array $attr=[])', 'php')?>
3383

@@ -37,7 +87,7 @@ static protected function default_options(){
3787
<li><code>$attr</code> is an array with optional attributes. Most of these are serialized and passed as-is to the input-field (e.g. passing <code>['data-foo' => 'bar']</code> results in <code>&lt;input&nbsp;data-foo="bar"&gt;</code>. Some fields have special options (see table below) which are consumed and not serialized.</li>
3888
</ul>
3989

40-
<h2 id="fields">Fields</h2>
90+
<h2 id="fields">Available fields</h2>
4191
<table class="table table-striped doc-table">
4292
<thead>
4393
<tr>
@@ -58,18 +108,6 @@ static protected function default_options(){
58108
</tbody>
59109
</table>
60110

61-
<h2 id="array">Array binding</h2>
62-
<div class="row">
63-
<div class="col-sm-6"><?php display('usage/array.php'); ?></div>
64-
<div class="col-sm-6"><?php include('usage/array.php'); ?></div>
65-
</div>
66-
67-
<h2 id="object">Object binding</h2>
68-
<div class="row">
69-
<div class="col-sm-6"><?php display('usage/object.php'); ?></div>
70-
<div class="col-sm-6"><?php include('usage/array.php'); ?></div>
71-
</div>
72-
73111
<h2 id="styling">Styling</h2>
74112
<table class="table table-striped">
75113
<thead>

docs/usage/unbound.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
Form::create('login', function($f){
4+
$f->text_field('username', 'Username');
5+
$f->password_field('password', 'Password');
6+
});

src/Form.php

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,9 @@ class Form extends FormContainer {
1010
'method' => 'post', /* form method (get or post) */
1111
'method_field_name' => '_method', /* name of the method field when method isn't GET or POST. */
1212
'action' => '', /* form action (if set to false no <form> wrapper will be generated (you must manually set it) */
13-
'enctype' => false, /* form encoding */
1413
'layout' => 'table', /* layout engine, one of {plain, table, bootstrap, unbuffered} or a class extending FormLayout. If unbuffered fields is written directly. */
1514
'prefix' => false, /* use a custom prefix in front of all names, default is nothing for arrays and class name for objects */
16-
'style' => '', /* add custom style to form, e.g. width */
1715
'class' => [], /* additional classes (accepts string or array) */
18-
'data' => [], /* extra data attributes */
19-
'attr' => [], /* extra arbitrary attributes */
2016
);
2117

2218
public $id = "";
@@ -26,7 +22,7 @@ class Form extends FormContainer {
2622
private $name_pattern = '%s';
2723
private $layout = null;
2824
private $callback = null;
29-
private $options = null;
25+
private $options = [];
3026
private $unbuffered = false;
3127

3228
/**
@@ -144,33 +140,59 @@ public function __construct($id, $callback, array $options=array()) {
144140
$this->render();
145141
}
146142

147-
private function parse_options($user){
148-
$options = array_merge(static::$base_options, static::default_options(), $user);
149-
150-
/* split classes given as string */
151-
if ( !is_array($options['class']) ){
152-
$options['class'] = explode(' ', $options['class']);
143+
protected function pop_attr($key, &$attr, &$value){
144+
if ( array_key_exists($key, $attr) ){
145+
$value = $attr[$key];
146+
unset($attr[$key]);
147+
return true;
153148
}
149+
return false;
150+
}
154151

155-
/* store raw options */
156-
$this->options = $options;
157-
158-
/* required */
159-
$this->attr['method'] = static::parse_method($options['method']);
160-
$this->attr['action'] = $options['action'];
152+
private function parse_options($user){
153+
$options = array_merge(
154+
static::$base_options,
155+
static::default_options(),
156+
$user
157+
);
158+
159+
/* extract non-attribute options */
160+
$this->pop_attr('method_field_name', $options, $this->options['method_field_name']);
161+
162+
/* layout */
163+
$this->pop_attr('layout', $options, $layout);
164+
$this->set_layout($layout);
165+
166+
/* rewrite requested action to GET/POST and store original method */
167+
$this->pop_attr('method', $options, $method);
168+
$this->options['requested_method'] = $method;
169+
$this->attr['method'] = static::parse_method($method);
170+
171+
/* custom prefix */
172+
$this->pop_attr('prefix', $options, $prefix);
173+
if ( $prefix ){
174+
$this->name_pattern = $prefix . '[%s]';
175+
}
161176

162-
/* optional */
163-
$this->set_layout($options);
164-
if ( $options['class'] ) $this->attr['class'] = array_merge($this->attr['class'], $options['class']);
165-
if ( $options['style'] ) $this->attr['style'] = $options['style'];
166-
if ( $options['prefix'] ) $this->name_pattern = $options['prefix'] . '[%s]';
167-
if ( $options['enctype'] ) $this->attr['enctype'] = $options['enctype'];
168-
if ( $options['data'] ) $this->attr['data'] = $options['data'];
177+
/* classes require deeper merge: classes has already been added */
178+
$this->pop_attr('class', $options, $class);
179+
$this->add_class($class);
180+
181+
/* merge form attributes */
182+
$attr = [];
183+
$this->pop_attr('attr', $options, $attr);
184+
$this->attr = array_merge(
185+
$this->attr,
186+
$options,
187+
$attr
188+
);
189+
}
169190

170-
/* arbitrary options */
171-
if ( $options['attr'] ){
172-
$this->attr = array_merge($this->attr, $options['attr']);
191+
protected function add_class($class){
192+
if ( is_string($class) ){
193+
$class = explode(' ', $class);
173194
}
195+
$this->attr['class'] = array_merge($this->attr['class'], $class);
174196
}
175197

176198
private function parse_method($method){
@@ -184,8 +206,7 @@ private function parse_method($method){
184206
}
185207
}
186208

187-
private function set_layout($options){
188-
$layout = $options['layout'];
209+
private function set_layout($layout){
189210
$class = $layout;
190211

191212
if ( is_string($layout) ){
@@ -209,7 +230,7 @@ private function set_layout($options){
209230
}
210231

211232
/* use layout class so it is possible to style a single layout */
212-
$this->attr['class'] = array_merge($this->attr['class'], array($class));
233+
$this->add_class($class);
213234

214235
$this->layout = $layout;
215236
}
@@ -268,7 +289,7 @@ public function hidden_field($key, $value=null, array $attr=array()) {
268289
}
269290

270291
public function method_field() {
271-
$this->hidden_field($this->options['method_field_name'], strtoupper($this->options['method']));
292+
$this->hidden_field($this->options['method_field_name'], strtoupper($this->options['requested_method']));
272293
}
273294

274295
protected function start() {
@@ -286,7 +307,7 @@ protected function start() {
286307
}
287308

288309
protected function end() {
289-
$method = strtoupper($this->options['method']);
310+
$method = strtoupper($this->options['requested_method']);
290311
if ( $method !== "GET" ) {
291312
$has_csrf = false;
292313
foreach( $this->hidden as $field) {

tests/FormTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,34 @@ public function testEmptyGroup(){
115115
$f->group(false, function($f){});
116116
}, ['layout' => $mock]);
117117
}
118+
119+
public function testArbitraryAttr(){
120+
$mock = new MockLayout();
121+
$form = Form::create('id', function($f){}, ['layout' => $mock, 'foobar' => 'spam']);
122+
$this->assertArrayHasKey('foobar', $mock->form_attr);
123+
$this->assertEquals('spam', $mock->form_attr['foobar']);
124+
}
125+
126+
public function testEnctypeDefault(){
127+
$mock = new MockLayout();
128+
$form = Form::create('id', function($f){}, ['layout' => $mock]);
129+
$this->assertArrayNotHasKey('enctype', $mock->form_attr);
130+
}
131+
132+
public function testEnctypeOption(){
133+
$mock = new MockLayout();
134+
$form = Form::create('id', function($f){}, ['layout' => $mock, 'enctype' => 'multipart/form-data']);
135+
$this->assertArrayHasKey('enctype', $mock->form_attr);
136+
$this->assertEquals('multipart/form-data', $mock->form_attr['enctype']);
137+
}
138+
139+
public function testNoExtraAttr(){
140+
$mock = new MockLayout();
141+
$form = Form::create('id', function($f){}, ['layout' => $mock]);
142+
$expected = ['class', 'method', 'action'];
143+
$this->assertEquals($expected, array_keys($mock->form_attr));
144+
145+
/* id is not passed via attribute but should also be present */
146+
$this->assertEquals('id', $mock->form_id);
147+
}
118148
}

tests/MockForm.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class MockLayout implements NitroXy\PHPForms\FormLayout {
1212
public $closed = 0;
1313

1414
public function preamble($form){
15-
$this->form_id = $form;
15+
$this->form_id = $form->id;
1616
$this->form_attr = $form->attr;
1717
$this->opened++;
1818
}

tests/phpunit.xml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
beStrictAboutTestsThatDoNotTestAnything="true"
77
verbose="true">
88

9-
<testsuites>
10-
<testsuite name="tests">
11-
<directory>.</directory>
12-
</testsuite>
13-
</testsuites>
9+
<testsuites>
10+
<testsuite name="tests">
11+
<directory>.</directory>
12+
</testsuite>
13+
</testsuites>
1414

15-
<filter>
15+
<filter>
1616
<whitelist addUncoveredFilesFromWhitelist="true">
1717
<directory>../src</directory>
1818
</whitelist>
19-
</filter>
19+
</filter>
2020
</phpunit>

0 commit comments

Comments
 (0)