Skip to content

Commit fe70a00

Browse files
author
Greg Bowler
authored
Implement and test boolean attributes (#257)
* Improve compatibility with traits' constant usage * Implement boolean attributes, closes #229 * Fix test regression * Add missing tests, closes #257
1 parent 41bad81 commit fe70a00

File tree

3 files changed

+109
-20
lines changed

3 files changed

+109
-20
lines changed

src/Element.php

+82-19
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
* @property string $className Gets and sets the value of the class attribute
1414
* @property-read TokenList $classList Returns a live TokenList collection of
1515
* the class attributes of the element
16-
* @property bool $checked Indicates whether the element is checked or not
17-
* @property bool $selected Indicates whether the element is selected or not
1816
* @property string $value Gets or sets the value of the element according to
1917
* its element type
2018
* @property string $id Gets or sets the value of the id attribute
@@ -26,27 +24,46 @@
2624
* @property string $innerText
2725
* @property-read StringMap $dataset
2826
*
27+
* @property bool allowfullscreen
28+
* @property bool allowpaymentrequest
29+
* @property bool async
30+
* @property bool autocapitalize
31+
* @property bool autocomplete
32+
* @property bool autofocus
33+
* @property bool autoplay
34+
* @property bool checked Indicates whether the element is checked or not
35+
* @property bool contentEditable
36+
* @property bool controls
37+
* @property bool default
38+
* @property bool defer
39+
* @property bool disabled
40+
* @property bool formnovalidate
41+
* @property bool hidden
42+
* @property bool ismap
43+
* @property bool loop
44+
* @property bool multiple
45+
* @property bool muted
46+
* @property bool novalidate
47+
* @property bool open
48+
* @property bool optimum
49+
* @property bool preload
50+
* @property bool readonly
51+
* @property bool required
52+
* @property bool reversed
53+
* @property bool selected Indicates whether the element is selected or not
54+
* @property bool typemustmatch
55+
*
2956
* @property string accept
3057
* @property string acceptCharset
3158
* @property string accessKey
3259
* @property string action
33-
* @property bool async
34-
* @property bool autofocus
35-
* @property bool autoplay
3660
* @property string alt
37-
* @property bool autocapitalize
38-
* @property bool autocomplete
3961
* @property string charset
4062
* @property string cite
4163
* @property string cols
42-
* @property bool contentEditable
43-
* @property bool controls
4464
* @property string data
4565
* @property string dateTime
46-
* @property bool defer
47-
* @property bool disabled
4866
* @property string dir
49-
* @property bool draggable
5067
* @property string download
5168
* @property string encoding
5269
* @property string enctype
@@ -58,24 +75,17 @@
5875
* @property string kind
5976
* @property string label
6077
* @property string lang
61-
* @property bool loop
6278
* @property string low
6379
* @property string min
6480
* @property string max
6581
* @property string maxLength
6682
* @property string mediaGroup
67-
* @property bool multiple
68-
* @property bool muted
6983
* @property string name
70-
* @property bool optimum
7184
* @property string pattern
7285
* @property string placeholder
7386
* @property string poster
74-
* @property bool preload
7587
* @property string readOnly
7688
* @property string rel
77-
* @property bool reversed
78-
* @property bool required
7989
* @property string rows
8090
* @property string start
8191
* @property string step
@@ -99,6 +109,31 @@ class Element extends DOMElement {
99109
use LiveProperty, NonDocumentTypeChildNode, ChildNode, ParentNode;
100110

101111
const VALUE_ELEMENTS = ["BUTTON", "INPUT", "METER", "OPTION", "PROGRESS", "PARAM"];
112+
const BOOLEAN_ATTRIBUTES = [
113+
"allowfullscreen",
114+
"allowpaymentrequest",
115+
"async",
116+
"autofocus",
117+
"autoplay",
118+
"checked",
119+
"controls",
120+
"default",
121+
"defer",
122+
"disabled",
123+
"formnovalidate",
124+
"hidden",
125+
"ismap",
126+
"loop",
127+
"multiple",
128+
"muted",
129+
"novalidate",
130+
"open",
131+
"readonly",
132+
"required",
133+
"reversed",
134+
"selected",
135+
"typemustmatch",
136+
];
102137

103138
/** @var TokenList */
104139
protected $liveProperty_classList;
@@ -381,6 +416,34 @@ protected function getRootDocument():Document {
381416
return $this->ownerDocument;
382417
}
383418

419+
private function getBooleanAttribute(string $attribute):bool {
420+
return $this->hasAttribute($attribute);
421+
}
422+
423+
private function setBooleanAttribute(string $attribute, bool $value) {
424+
if(($this->tagName === "input" && $this->type === "radio" && $attribute === "checked")
425+
|| ($this->tagName === "option" && !$this->parentNode->hasAttribute("multiple")) && $attribute === "selected") {
426+
if($form = $this->closest("form")) {
427+
$elementName = $this->getAttribute("name");
428+
if(!$elementName && $this->tagName === "option") {
429+
$elementName = $this->parentNode->getAttribute("name");
430+
}
431+
432+
$this->removeAttributeFromNamedElementAndChildren(
433+
$form,
434+
$elementName,
435+
$attribute
436+
);
437+
}
438+
}
439+
if($value) {
440+
$this->setAttribute($attribute, $attribute);
441+
}
442+
else {
443+
$this->removeAttribute($attribute);
444+
}
445+
}
446+
384447
private function value_set_select(string $newValue):void {
385448
$options = $this->getElementsByTagName('option');
386449
$selectedIndexes = [];

src/LiveProperty.php

+12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public function __set($name, $value) {
2020
}
2121

2222
private function __get_live($name) {
23+
if(defined("self::BOOLEAN_ATTRIBUTES")) {
24+
if(in_array($name, self::BOOLEAN_ATTRIBUTES)) {
25+
return $this->getBooleanAttribute($name);
26+
}
27+
}
28+
2329
$methodName = "prop_get_$name";
2430
if(method_exists($this, $methodName)) {
2531
return $this->$methodName();
@@ -36,6 +42,12 @@ private function __get_live($name) {
3642
}
3743

3844
private function __set_live($name, $value) {
45+
if(defined("self::BOOLEAN_ATTRIBUTES")) {
46+
if(in_array($name, self::BOOLEAN_ATTRIBUTES)) {
47+
return $this->setBooleanAttribute($name, $value);
48+
}
49+
}
50+
3951
$methodName = "prop_set_$name";
4052
if(method_exists($this, $methodName)) {
4153
return $this->$methodName($value);

test/phpunit/ElementTest.php

+15-1
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ public function testPropertyAttributeCorrelationBoolean() {
455455
$input = $document->querySelector("input");
456456
$input->autofocus = true;
457457
self::assertTrue($input->autofocus);
458-
self::assertNull($input->getAttribute("autofocus"));
458+
self::assertNotEmpty($input->getAttribute("autofocus"));
459459
}
460460

461461
public function testPropertyDataset() {
@@ -637,4 +637,18 @@ public function testDebugInfoAnchor() {
637637
];
638638
self::assertEquals($expected, $sut);
639639
}
640+
641+
public function testBooleanAttributes() {
642+
$document = new HTMLDocument();
643+
foreach(Element::BOOLEAN_ATTRIBUTES as $attribute) {
644+
$tagName = uniqid("custom-element-");
645+
$element = $document->createElement($tagName);
646+
647+
self::assertFalse($element->hasAttribute($attribute));
648+
self::assertFalse($element->$attribute);
649+
$element->$attribute = true;
650+
self::assertTrue($element->$attribute);
651+
self::assertTrue($element->hasAttribute($attribute));
652+
}
653+
}
640654
}

0 commit comments

Comments
 (0)