Skip to content

Commit cd918f4

Browse files
Update React.php
1 parent 44ddeb1 commit cd918f4

File tree

1 file changed

+47
-40
lines changed

1 file changed

+47
-40
lines changed

React.php

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,39 @@ abstract class Component{
66
private static $isTagsSet = false; //flag to track if all html tag classes created
77

88
//all html tags that are allowed
9-
private static $htmlTags = ['div','p','img','a','ul','li', 'h1','h2','h3','h4','h5','h6','iframe','article', 'form','input','textarea','select','option', 'link', 'script', 'button', 'nav', 'title', 'meta', 'code', 'pre', 'span', 'i', 'svg', 'path', 'circle', 'g'];
9+
private static $htmlTags = ['div','p','img','small', 'a','ul','li', 'h1','h2','h3','h4','h5','h6','iframe','article', 'form','input','textarea','select','option', 'link', 'script', 'button', 'nav', 'title', 'meta', 'code', 'pre', 'span', 'i', 'svg', 'path', 'circle', 'g'];
1010

1111
private static $hasNoChild = ['img', 'link', 'input', 'meta']; //tags that have no children
1212
private const tagNameSpace= 'React\Tag'; //name space for the tags
1313

14-
private static $counter = 1; // counter for generating sequencial id
15-
protected $id = ''; //the current id of the component
1614
protected $state = []; //the current state
15+
protected $props = []; //the props
16+
protected $children = []; //the children
17+
private static $queue = []; //queue of components
18+
private static $isQueued = false;
1719

1820
/*
1921
run the first time when first component called
2022
responsible for:
2123
- setting all tags class component
2224
- setting the script that controls the state
2325
*/
24-
private function setTags(){
26+
static function setTags(){
2527
@ob_start();
2628
foreach(self::$htmlTags as $el){
2729
eval("namespace ". self::tagNameSpace ."; class $el extends \React\Component{}");
2830
}
2931
self::$isTagsSet = true;
3032

3133
//script tag to setup setState function
32-
echo new \React\Tag\script('!function(t,e){var n=function(){e.querySelectorAll("[component-id] *:not([component-id])").forEach(function(t){t.setState||(t.getState=i,t.setState=o)})},o=function(t,o){var i=this.closest("[component-id]");if(i){var a=i.getAttribute("component-id"),r=this.getAttribute("key"),c=(document.activeElement,this.value),s=this.getState();"function"==typeof t&&(t=t(s));var u=new XMLHttpRequest,d={id:a,state:t,prevState:s};u.onreadystatechange=function(){if(4==this.readyState&&200==this.status){if(i.outerHTML=this.responseText,r){var t=e.querySelector("[component-id='"+a+"'] [key='"+r+"']");t&&(t.focus(),c&&(t.value="",t.value=c))}"function"==typeof o&&o(),n()}},u.open("POST",location.href,!0),u.setRequestHeader("Content-type","application/x-www-form-urlencoded"),u.send("phpreact="+JSON.stringify(d))}},i=function(){try{var t=this.closest("[component-id]");return JSON.parse(t.getAttribute("component-state"))}catch(t){return{}}};t.addEventListener("load",n)}(window,document);');
34+
echo new \React\Tag\script('!function(t,e){var n=function(){e.querySelectorAll("[component] *").forEach(function(t){t.setState||(t.getState=c,t.setState=o)})},o=function(t,o){var c=this.hasAttribute("component")?this:this.closest("[component]");if(c){var i=[c.getAttribute("component")],r=this.getAttribute("key"),s=(document.activeElement,this.value),a=this.getState();c.querySelectorAll("[component]").forEach(function(t){i.push(t.getAttribute("component"))}),"function"==typeof t&&(t=t(a));var u=new XMLHttpRequest,p={components:i,state:t};u.onreadystatechange=function(){if(4==this.readyState&&200==this.status&&this.responseText){var t,i=e.createElement("div");i.innerHTML=this.responseText,r&&(t=i.querySelector("[key=\'"+r+"\']")),c.replaceWith(i.childNodes[0]),t&&(t.focus(),s&&(t.value="",t.value=s)),"function"==typeof o&&o(),n()}},u.open("POST",location.href,!0),u.setRequestHeader("Content-type","application/x-www-form-urlencoded"),u.send("phpreact="+JSON.stringify(p))}},c=function(){try{var t=this.closest("[component]");return JSON.parse(t.getAttribute("component-state"))}catch(t){return{}}};t.addEventListener("load",n)}(window,document);');
3335
}
3436

3537
/*
3638
@return the current component tag name
3739
*/
3840
protected function getTagName(){
39-
return trim(str_replace(self::tagNameSpace, '', get_class($this)), '\\');
41+
return strtolower(trim(str_replace(self::tagNameSpace, '', get_class($this)), '\\'));
4042
}
4143

4244
/*
@@ -76,7 +78,7 @@ static function setHasNoChild($tags){
7678
@return: parsed array string
7779
*/
7880
private static function parseTags($tags){
79-
return array_map(function($tag){ return self::parseAttribute($tag); }, (array)$tags);
81+
return array_map(function($tag){ return strtolower(self::parseAttribute($tag)); }, (array)$tags);
8082
}
8183

8284
/*
@@ -117,16 +119,42 @@ function render(){
117119
return "<$tag $attributes>$children</$tag>";
118120
}
119121

122+
private function getQueueComponent(){
123+
$encode = array_shift(self::$queue);
124+
return $encode ? unserialize(base64_decode($encode)) : null;
125+
}
126+
127+
private function stateManager(){
128+
$component = null;
129+
130+
if(!self::$queue && !self::$isQueued){
131+
if($this->isHtmlTage()) return '';
132+
$post = json_decode($_POST['phpreact']);
133+
self::$queue = $post->components;
134+
self::$isQueued = true;
135+
$component = $this->getQueueComponent();
136+
$oldState = $component->state;
137+
$component->state = (object)array_merge((array)$oldState, (array)$post->state);
138+
$component->componentDidUpdate($oldState, $component->state);
139+
}elseif(!$this->isHtmlTage()){
140+
$component = $this->getQueueComponent();
141+
}
142+
143+
if(!$component) $component = $this;
144+
145+
return $component->handleRender();
146+
}
147+
120148
/*
121149
parse the components to html
122150
@return: html string
123151
*/
124-
function __toString(){
152+
private function handleRender(){
125153
$components = $this->render();
126154

127155
//save state of custom component in top html wrapper
128156
if(!$this->isHtmlTage() && $components instanceof Component && $components->isHtmlTage()){
129-
$components->props = (object)array_merge((array)$components->props, ['component-id'=> $this->id, 'component-state'=> $this->state]);
157+
$components->props = (object)array_merge((array)$components->props, ['component'=> base64_encode(serialize($this)), 'component-state'=> $this->state]);
130158
}
131159

132160
if(!is_array($components)) $components = [$components]; //must be list of components
@@ -137,6 +165,14 @@ function __toString(){
137165
return implode('', $components);
138166
}
139167

168+
/*
169+
parse the components to html
170+
@return: html string
171+
*/
172+
function __toString(){
173+
return empty($_POST['phpreact']) ? $this->handleRender() : $this->stateManager();
174+
}
175+
140176
/*
141177
construct the tag with list of child component and props
142178
@param: $children: component|array[of component]
@@ -145,19 +181,15 @@ function __toString(){
145181
@usage: Component($children, $props) or Component($props) if exists in hasNoChild
146182
*/
147183
function __construct($children = [], $props = []){
148-
if(!self::$isTagsSet) $this->setTags();
184+
if(!self::$isTagsSet) self::setTags();
149185
$hasNoChild = $this->hasNoChild();
150-
$this->setId();
151186

152187
if(!is_array($children)) $children = [$children];
153188

154189
//set properties
155-
$this->props = (object)($hasNoChild ? $children : $props);
190+
$this->props = (object)array_merge((array)$this->props, $hasNoChild ? $children : $props);
156191
$this->children = $hasNoChild ? [] : $children;
157192
$this->state = (object)$this->state;
158-
159-
//listen to state change
160-
$this->setStateListener();
161193
}
162194

163195
/*
@@ -166,29 +198,4 @@ function __construct($children = [], $props = []){
166198
@param: $currentState: [object] the current state
167199
*/
168200
function componentDidUpdate($oldState, $currentState){}
169-
170-
/*
171-
set unique id of each custom component
172-
it's used to check against when state updated
173-
*/
174-
private function setId(){
175-
if($this->isHtmlTage()) return;
176-
$this->id = md5(self::$counter); //generate id
177-
self::$counter++;
178-
}
179-
180-
/*
181-
ajax listner of state update
182-
ajax posts 'phpreact' with json that has attributes [id: component id, state: the new state, prevState: the current state];
183-
*/
184-
private function setStateListener(){
185-
if(empty($_POST['phpreact'])) return;
186-
$post = json_decode($_POST['phpreact']);
187-
if(!$post || $post->id != $this->id) return;
188-
$oldState = $post->prevState;
189-
$this->state = (object)array_merge((array)$oldState, (array)$post->state);
190-
$this->componentDidUpdate($oldState, $this->state);
191-
@ob_end_clean();
192-
die($this);
193-
}
194201
}

0 commit comments

Comments
 (0)