@@ -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