Browse Source

TODO: U SidePanelButton

Piotr Labudda 5 năm trước cách đây
mục cha
commit
e1525e50f7

+ 17 - 2
SE/se-lib/UI/AjaxContent.php

@@ -10,10 +10,25 @@ class UI_AjaxContent implements UITagInterface {
 	 * @return string html code
 	 */
 	static function h($tagName, $props = [], $childrens = []) {
-		if (empty($props['url'])) throw new Exception("Missing url!");
 		// if (empty($props['url'])) throw new Exception("Missing url!");
+		// if (empty($props['url'])) throw new Exception("Missing url!");
+		$idHtmlNode = self::generateUniqueID();
+		$loading = V::get('loading', "Wczytywanie danych ...", $props);
 
-		return UI::h('div', [], "loading...");
+		return UI::h(null, [], [
+			UI::h('div', [ 'id' => $idHtmlNode ], $loading),
+			UI::hScript($jsFile = __FILE__ . '.script.js', $jsonVars = [
+				'ID_HTML_NODE' => $idHtmlNode,
+				'URL_FETCH_CONTENT' => $props['url'],
+				'LOADING' => $loading,
+			]),
+		]);
 	}
 
+	static function generateUniqueID() {
+		static $_counter = 0;
+
+		$_counter += 1;
+		return "p5_ajax_content_{$_counter}";
+	}
 }

+ 136 - 0
SE/se-lib/UI/AjaxContent.php.script.js

@@ -0,0 +1,136 @@
+var DBG = 0;
+var DBG1 = 1;
+if (!ID_HTML_NODE) throw "Missing 'ID_HTML_NODE'!";
+// if (!URL_FETCH_CONTENT) throw "Missing 'URL_FETCH_CONTENT'!";
+var URL_FETCH_CONTENT = URL_FETCH_CONTENT || '';
+if (!global.fetch) throw "Missing 'fetch'!";
+if (!global.p5VendorJs.ReactDOM) throw "Missing ReactDOM"
+if (!global.p5UI__buildDom) throw "Missing 'p5UI__buildDom'!"; // static/vendor.js, static/p5UI/buildDom.js
+if (!global.P5UI__AjaxContent) throw "Missing 'P5UI__AjaxContent'!"; // static/vendor.js, static/p5UI/AjaxContent.js
+var NOTIFY_USER_MODE = NOTIFY_USER_MODE || 'inline'; // inline | notify
+var LOADING = LOADING || 'loading ...';
+var ReactDOM = global.p5VendorJs.ReactDOM;
+var h = global.p5VendorJs.React.createElement;
+var P5UI__AjaxContent = global.P5UI__AjaxContent;
+
+var rootNode = document.getElementById(ID_HTML_NODE);
+if (!rootNode) throw "Node not exists";
+
+ReactDOM.render(
+	h(P5UI__AjaxContent, {
+		url: URL_FETCH_CONTENT,
+		loading: LOADING,
+	}),
+	rootNode
+)
+
+return;
+
+global.fetch(URL_FETCH_CONTENT, {
+	credentials: "same-origin",
+}).then(function (response) {
+	DBG1 && console.log("DBG:response", { status: response.status, response: response });
+	response.text()
+		.then(response.ok ? _handleFetchResponseSuccess : _handleFetchResponseFail)
+		.catch(function (err) {
+			DBG1 && console.log("Response Error #1:")
+			DBG1 && console.error(err)
+			// throw err;
+			doNotify('error', "" + err);
+		})
+}).then(function (data) {
+	DBG1 && console.warn("DBG:response data", { data });
+}).catch(function (err) {
+	DBG1 && console.log("Response Error #2:")
+	DBG1 && console.error(err)
+})
+
+
+function _handleFetchResponseFail(responseText) {
+	DBG1 && console.warn("DBG:response fail responseText", { responseText });
+	if (!responseText) throw "Error";
+	_handleFetchResponseText(false, responseText)
+}
+function _handleFetchResponseSuccess(responseText) {
+	DBG1 && console.warn("DBG:response success responseText", { responseText });
+	_handleFetchResponseText(true, responseText)
+}
+function _handleFetchResponseText(isOk, responseText) {
+	if ('{' !== responseText.substr(0, 1)) throw "Response Parse Error: Expected json.";
+	try {
+		var jsonResponse = JSON.parse(responseText);
+		if (!jsonResponse.msg) throw "Response Parse Error: Expected json with msg.";
+		if (!isOk) throw jsonResponse.msg; // TODO: throw or just show alert for user
+
+		_handleFetchResponseJson(jsonResponse)
+	} catch (err) {
+		throw "Response Parse Error: " + err;
+	}
+}
+function _handleFetchResponseJson(jsonResponse) {
+	switch (jsonResponse.type) {
+		case 'error': return doNotify('error', (jsonResponse.msg || "Wystąpił błąd"));
+		case 'success': return doShowContent(jsonResponse.body);
+		default: return doNotify('warning', jsonResponse.msg);
+	}
+}
+
+function doNotify(type, msg) {
+	// TODO: if (NOTIFY_USER_MODE) // 'inline' | 'notify'
+	type = ('error' == type) ? 'danger' : type;
+	rootNode.innerHTML = '<div class="alert alert-' + type + '" style="margin-bottom:0">' + msg + '</div>';
+}
+function doShowContent(body) {
+	if (body.reactNode) return p5UI__buildDom(body.reactNode, rootNode);
+	if (body.html) return doShowHtmlContent(body.html, rootNode);
+	if (typeof body === 'string' || body instanceof String) return doShowHtmlContent(body, rootNode);
+	else {
+		// TODO: if DBG
+		rootNode.innerHTML = '<pre>' + JSON.stringify(body, null, 4) + '</pre>';
+	}
+}
+function doShowHtmlContent(htmlContent, node) {
+	node.innerHTML = htmlContent;
+}
+// }).then(function (response) {
+// 	return response.text()
+// }).then(function (responseText) {
+// 	try {
+// 		return JSON.parse(responseText)
+// 	} catch (e) {
+// 		throw responseText
+// 	}
+// }).then(function (result) {
+// 	if ('success' == result.type) {
+// 		p5UI__notifyAjaxCallback(result)
+// 		resolve(result.data)
+// 	} else {
+// 		p5UI__notifyAjaxCallback(result)
+// 		reject(result.msg || "Wystąpił błąd!")
+// 	}
+// }).catch(function (e) {
+// 	reject("ajax response error: " + e)
+// });
+
+
+
+// .fetch(P5MENU_URL
+// 	, (!postData)
+// 		?	{ method: 'GET',
+// 				headers: { 'Content-Type': 'application/json' },
+// 				credentials: 'same-origin',
+// 			}
+// 		:	{ method: 'POST',
+// 				headers: { 'Content-Type': 'application/json' },
+// 				credentials: 'same-origin',
+// 				body: JSON.stringify(postData)
+// 			}
+// ).then(function (response) {
+// 	return response.json()
+// }).then(function (response) {
+// 	if ('success' === response.type) {
+// 		_update(response.body)
+// 	} else {
+// 		// err...
+// 	}
+// })

+ 1 - 1
SE/se-lib/UI/SidePanelButton.php

@@ -28,7 +28,7 @@ class UI_SidePanelButton implements UITagInterface {
 				if (empty($childrens)) throw new Exception("Missing childrens");
 			} break;
 			case 'dynamic': {
-				throw new Exception("TODO: mode '{$mode}'");
+				if (empty($props['url'])) throw new Exception("Missing 'url'");
 			} break;
 			default: throw new Exception("Not implemented mode '{$mode}'");
 		}

+ 199 - 0
SE/static/p5UI/AjaxContent.js

@@ -0,0 +1,199 @@
+(function (global, p5VendorJs) {
+	if (!p5VendorJs.React) throw "Missing React"
+	if (!p5VendorJs.ReactDOM) throw "Missing ReactDOM"
+	if (!p5VendorJs.createReactClass) throw "Missing createReactClass"
+
+	var DBG = DBG || 0;
+	var DBG1 = 1;
+
+	/**
+	 * Loads props.url after mount.
+	 * If props.url changed: clear, start loding new url.
+	 * If props.url is empty: clear.
+	 * 
+	 * Clear: show props.emptyUrlContent || ''
+	 * 
+	 * @usage:
+	 *   ReactDOM.render(
+	 * 	     h(P5UI__AjaxContent, {
+	 *           url: '',
+	 * 	     }),
+	 * 	     document.getElementById(HTML_ID)
+	 *   )
+	 * 
+	 * props.url: url | null
+	 * props.loading: html | null
+	 * props.emptyContent: html | null - TODO: show when url is not defined
+	 * 
+	 */
+
+
+	// var React = window.p5VendorJs.React;
+	var createReactClass = window.p5VendorJs.createReactClass;
+	var h = window.p5VendorJs.React.createElement;
+
+	// 	componentDidMount: function () { console.log('MyWidget::componentDidMount...'); },
+	// 	componentWillReceiveProps: function (nextProps) { console.log('MyWidget::componentWillReceiveProps(nextProps)...', nextProps); },
+	// 	shouldComponentUpdate: function (nextProps, nextState) { console.log('MyWidget::shouldComponentUpdate(nextProps, nextState)...', nextProps, nextState); },
+	// 	componentWillUpdate: function (nextProps, nextState) { console.log('MyWidget::componentWillUpdate(nextProps, nextState)...', nextProps, nextState); },
+	// 	componentDidUpdate: function (prevProps, prevState) { console.log('MyWidget::componentDidUpdate(prevProps, prevState)...', prevProps, prevState); },
+	// 	componentWillUnmount: function () { console.log('MyWidget::componentWillUnmount...'); },
+	var P5UI__AjaxContent = createReactClass({
+		_refContentEl: null,
+		setContentElRef: function (el) {
+			this._refContentEl = el;
+		},
+		getInitialState: function () {
+			return {
+				isLoading: false,
+				postData: null, // if method = 'POST'
+				response_type: null,
+				response_msg: null,
+				response_body: null,
+			};
+		},
+		componentDidMount() {
+			if (this.props.url) this._fetchContent();
+		},
+		componentDidUpdate(prevProps, prevState) {
+			if (prevProps.url !== this.props.url) {
+				this._fetchContent();
+			}
+		},
+
+		_fetchContent: function () {
+			this.setState({
+				isLoading: true
+			});
+			// var _setState = this.setState.bind(this);
+			var _handleFetchResponseSuccess = this._handleFetchResponseSuccess.bind(this);
+			var _handleFetchResponseFail = this._handleFetchResponseFail.bind(this);
+			var _handleFetchResponseJson = this._handleFetchResponseJson.bind(this);
+			window.fetch(this.props.url,
+				(!this.props.postData)
+				?	{
+						method: 'GET',
+						headers: { 'Content-Type': 'application/json' },
+						credentials: 'same-origin',
+					}
+				:	{
+						method: 'POST',
+						headers: { 'Content-Type': 'application/json' },
+						credentials: 'same-origin',
+						body: this.props.postData, // JSON.stringify(this.props.postData)
+					}
+			).then(function (response) {
+				DBG1 && console.log("DBG:response", {
+					status: response.status,
+					response: response
+				});
+				response.text()
+				.then(response.ok ? _handleFetchResponseSuccess : _handleFetchResponseFail)
+				.catch(function (err) {
+					_handleFetchResponseJson({
+						response_type: 'error',
+						response_msg: "" + err,
+						response_body: null,
+					})
+				})
+			}).then(function (data) {
+				DBG1 && console.warn("DBG:response data", { data });
+			}).catch(function (err) {
+				DBG1 && console.log("Response Error #2:")
+				DBG1 && console.error(err)
+			})
+		},
+
+		_handleFetchResponseFail: function (responseText) {
+			DBG1 && console.warn("DBG:response fail responseText", { responseText });
+			if (!responseText) throw "Error";
+			this._handleFetchResponseText(false, responseText)
+		},
+
+		_handleFetchResponseSuccess: function (responseText) {
+			DBG1 && console.warn("DBG:response success responseText", { responseText });
+			this._handleFetchResponseText(true, responseText)
+		},
+
+		_handleFetchResponseText: function (isOk, responseText) {
+			DBG1 && console.warn("DBG:response _handleFetchResponseText", { isOk, responseText });
+			if ('{' !== responseText.substr(0, 1)) throw "Response Parse Error: Expected json.";
+			try {
+				var jsonResponse = JSON.parse(responseText);
+				if (!jsonResponse.msg) throw "Response Parse Error: Expected json with msg.";
+				if (!isOk) throw jsonResponse.msg; // TODO: throw or just show alert for user
+
+				this._handleFetchResponseJson(jsonResponse)
+			} catch (err) {
+				throw "Response Parse Error: " + err;
+			}
+		},
+
+		_handleFetchResponseJson: function (jsonResponse) {
+			DBG1 && console.warn("DBG:response _handleFetchResponseJson", { jsonResponse });
+			this.setState({
+				isLoading: false,
+				response_type: jsonResponse.type,
+				response_msg: jsonResponse.msg,
+				response_body: jsonResponse.body,
+			})
+		},
+
+		shouldComponentUpdate: function (nextProps, nextState) {
+			DBG1 && console.warn("DBG:response shouldComponentUpdate", {
+				props: this.props, nextProps,
+				state: this.state, nextState,
+			});
+			if (!this.state.isLoading && nextState.isLoading) {
+				doShowLoading(this.props, this._refContentEl);
+			}
+			if (!nextState.isLoading && nextState.response_type) {
+				doParseResponseBody(nextState.response_type, nextState.response_msg, nextState.response_body, this._refContentEl);
+			}
+			return false; // render only once!
+		},
+		render: function () {
+			DBG1 && console.log('DBG:render (TODO: render only once!)', { options: this.state.options });
+
+			return h('div', {
+				ref: this.setContentElRef,
+				// dangerouslySetInnerHTML: { __html: this.renderContentHtml() }
+			});
+		}
+	});
+
+	function doParseResponseBody(type, msg, body, rootNode) {
+		switch (type) {
+			case 'error': return doNotify('error', (msg || "Wystąpił błąd"), rootNode);
+			case 'success': return doShowContent(body, rootNode);
+			default: return doNotify('warning', msg, rootNode);
+		}
+	}
+	function doShowLoading(props, rootNode) {
+		DBG1 && console.log('DBG:doShowLoading', { rootNode });
+		if (!rootNode) return;
+		rootNode.innerHTML = props.loading || '<p>Loading ...</p>';
+		// DBG && rootNode.innerHTML += "\n" + '<pre>' + "props: " + JSON.stringify(props, null, 4) + '</pre>';
+	}
+	function doShowContent(body, rootNode) {
+		DBG1 && console.log('DBG:doShowContent', { rootNode });
+		if (!rootNode) return;
+		if (body.reactNode) return p5UI__buildDom(body.reactNode, rootNode);
+		if (body.html) return doShowHtmlContent(body.html, rootNode);
+		if (typeof body === 'string' || body instanceof String) return doShowHtmlContent(body, rootNode);
+		else {
+			// TODO: if DBG
+			rootNode.innerHTML = '<pre>' + JSON.stringify(body, null, 4) + '</pre>';
+		}
+	}
+	function doShowHtmlContent(htmlContent, node) {
+		node.innerHTML = htmlContent;
+	}
+	function doNotify(type, msg, rootNode) {
+		// TODO: if (NOTIFY_USER_MODE) // 'inline' | 'notify'
+		type = ('error' == type) ? 'danger' : type;
+		rootNode.innerHTML = '<div class="alert alert-' + type + '" style="margin-bottom:0">' + msg + '</div>';
+	}
+
+	global.P5UI__AjaxContent = P5UI__AjaxContent;
+})(window, window.p5VendorJs);