TIP게시판

제목 nested panel - sk-rt / handy-collapse
글쓴이 darkninja 작성시각 2023/02/25 23:23:23
댓글 : 1 추천 : 0 스크랩 : 0 조회수 : 438   RSS

https://github.com/sk-rt/handy-collapse

취미로 코딩을 하는 것은 정말 재미있고 흥미로운 것입니다.

초보자들이 코딩에 빠져들수 있는 재미를 가져볼수 있게 만들고 싶었는데

코드조각들이 점점 암호문처럼 되어가는.


<!DOCTYPE html>

<html>

<head>
	<meta charset="utf-8" />
	<title>Test page</title>

	<style>
		pre
		{
			padding: 0px;
			border-left: 3px solid #ccc;
			border-right: 2px solid #ccc;
			margin: 0px;
			overflow: auto;	width: 99.7%;
			background-color: #F5F5F5;
		}
	</style>

	<style>
		/* handy-collapse */
		/* top, right, bottom, left */

		.handy-panel
		{
			background: #eee;
			margin: 1px;
			border: 1px solid #CCC;
			-moz-border-radius: 3px;
			-webkit-border-radius: 3px;
		}

		.handy-subpanel
		{ 
			background: #eee;
			margin: 1px 10px 1px 10px;
			border: 1px solid #CCC;
			-moz-border-radius: 3px;
			-webkit-border-radius: 3px;
		}

		.handy-panel.collapsed,
		.handy-subpanel.collapsed
		{
			border: 1px solid #999;
			-moz-border-radius: 3px;
			-webkit-border-radius: 3px;
		}

		.handy-panel.expand,
		.handy-subpanel.expand
		{
			border-left: 1px solid #CCC;
			border-right: 1px solid #CCC;
			border-bottom: 1px solid #CCC;
			-moz-border-radius: 3px;
			-webkit-border-radius: 3px;
		}

		.handy-panel > button,
		.handy-subpanel > button
		{
			margin: 0px;
			padding: 2px 2px 2px 5px;
			background: #CCC url(images/arrow-up.gif) no-repeat 99.2% center;
			border: 0px;
			-moz-border-radius: 3px;
			-webkit-border-radius: 3px;
		}

		.handy-panel > button:hover,
		.handy-subpanel > button:hover { 
			background-color: #A9BCEF; 
			cursor:pointer;
		}

		.handy-panel.collapsed > button,
		.handy-subpanel.collapsed > button
		{
			margin: 0px;
			padding: 2px 2px 2px 5px;
			background: #CCC url(images/arrow-dn.gif) no-repeat 99.2% center;
			border-color: #CCC;
			-moz-border-radius: 3px;
			-webkit-border-radius: 3px;
		}

		.handy-panel.collapsed > button:hover,
		.handy-subpanel.collapsed > button:hover
		{
			margin: 0px;
			padding: 2px 2px 2px 5px;
			background: #A9BCEF url(images/arrow-dn.gif) no-repeat 99.2% center;
			-moz-border-radius: 3px;
			-webkit-border-radius: 3px;
		}

		.handy-panelcontent
		{ 
			padding: 2px 3px 2px 4px; 
			margin: 0px 0px 0px 0px;
			overflow: hidden;
			list-style-type: none;
		}

		.handy-panelcontent.is-1 { background-color:#FFFFFF; }
		.handy-panelcontent.is-2 { background-color:#F7F7F7; }
		.handy-panelcontent.is-3 { background-color:#EEEEEE; }
		.handy-panelcontent.is-4 { background-color:#E7E7E7; }
		.handy-panelcontent.is-5 { background-color:#CACACA; }


		.handy-panelcontent p
		{ 
			margin: 0px; 
			padding: 0px 0px 2px 0px; 
		}

		/* collapsed panel content */
		.handy-panel.collapsed .handy-panelcontent { display: none; }
		.handy-subpanel.collapsed .handy-panelcontent { display: none; }
	
		.button.is-fullwidth{
			display:flex;
			width:100%
		}
	</style>

</head>

<body>
	<?php
		//source
		//https://www.media-division.com/javascript-animated-collapsible-panels-without-frameworks/
		//https://github.com/sk-rt/handy-collapse
		//https://www.cssscript.com/nested-accordion-content-toggle-handy-collapse/
		//https://www.cssscript.com/demo/Nested-Accordion-Content-Toggle-handy-collapse/
		//https://stackoverflow.com/questions/6054033/pretty-printing-json-with-php
		//https://github.com/LorDOniX/json-viewer
		//https://github.com/JAAulde/cookies
	?>
	
	<div class="handy-panel left expand">
		<button type="button" class="button is-fullwidth" data-nested-button="handy-panel-nested">handy-panel-nested</button>
		<div class="handy-panelcontent is-1" data-nested-content="handy-panel-nested">
			<button type="button" id="loadjsondata">load jsondata</button>
		
			<div class="handy-panel left">
				<button type="button" class="button is-fullwidth" data-nested-button="handy-panel">handy-panel</button>
				<div class="handy-panelcontent is-1" data-nested-content="handy-panel">
					<div id="handycookiesArray" name="handycookiesArray"></div>
					<div id="allcookiesArray" name="allcookiesArray"></div>
				</div>
			</div>
			
			<div class="handy-panel left">
				<button type="button" class="button is-fullwidth" data-nested-button="jsonformatview">jsonformatview</button>
				<div class="handy-panelcontent is-1" data-nested-content="jsonformatview">
					<div id="disp_format_json" name="disp_format_json">0</div>
					<div id="disp_format_json1" name="disp_format_json1">1</div>
					<div id="disp_format_json2" name="disp_format_json2">2</div>
				</div>
			</div>

		</div>
	</div>

	<script type="text/javascript">
		var HANDY_PANEL           = "handy-panel";
		var HANDY_SUBPANEL        = "handy-subpanel";
		var HANDY_PANELCONTENT    = "handy-panelcontent";

		var HANDY_NAMESPACE_HEAD  = "data"
		var HANDY_NAMESPACE       = "basic"
		var HANDY_HEADING_TAG     = "button";
		var HANDY_CONTENT_TAG     = "content";
	
		var HANDY_EXPAND_CLASS    = "expand";
		var HANDY_COLLAPSED_CLASS = "collapsed";

		var HANDY_COOKIE_NAME     = "handy-panels";
		var HANDY_CSS_EASING      = "ease-in-out";

		var HANDY_ANIMATION_DELAY = 400; /*ms*/
		var HANDY_ANIMATION_STEPS = 10;

		var HANDY_PANEL_LEFT      = "left";
		var HANDY_PANEL_RIGHT     = "right";

		var _typeof = 
			typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? 
				function(obj) { return typeof obj; } : 
				function(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

		var _extends = Object.assign || function(target) 
		{
			for (var i = 1; i < arguments.length; i++) {
				var source = arguments[i];
				for (var key in source) {
					if (Object.prototype.hasOwnProperty.call(source, key)) {
						target[key] = source[key];
					}
				}
			}
			return target;
		};

		var _createClass = function() 
		{
			function defineProperties(target, props) {
				for (var i = 0; i < props.length; i++) {
					var descriptor = props[i];
			
					descriptor.enumerable = descriptor.enumerable || false;
					descriptor.configurable = true;
			
					if ("value" in descriptor) descriptor.writable = true;
			
					Object.defineProperty(target, descriptor.key, descriptor);
				}
			}
			return function(Constructor, protoProps, staticProps) {
				if (protoProps) defineProperties(Constructor.prototype, protoProps);
				if (staticProps) defineProperties(Constructor, staticProps);
		
				return Constructor;
			};
		}();
 
		function _classCallCheck(instance, Constructor) 
		{
			if (!(instance instanceof Constructor)) {
				throw new TypeError("Cannot call a class as a function");
			}
		}

		var HandyCollapse = function() 
		{
			function HandyCollapse() 
			{
				var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
		
				_classCallCheck(this, HandyCollapse);
		
				_extends(this, {
					nameSpace: HANDY_NAMESPACE,
					toggleHeadingAttr: HANDY_NAMESPACE_HEAD + "-" + (options.nameSpace || HANDY_NAMESPACE) + "-" + HANDY_HEADING_TAG,
					toggleContentAttr: HANDY_NAMESPACE_HEAD + "-" + (options.nameSpace || HANDY_NAMESPACE) + "-" + HANDY_CONTENT_TAG,
					isAimation: true,
					closeOthers: true,
					animatinSpeed: HANDY_ANIMATION_DELAY,
					cssEasing: HANDY_CSS_EASING,
					cookie_name : HANDY_COOKIE_NAME + "-" + options.nameSpace,
					onSlideStart: function onSlideStart() {
						return false;
					},
					onSlideEnd: function onSlideEnd() {
						return false;
					}
				}, options);
		
				this.toggleHeadingEls = document.querySelectorAll("[" + this.toggleHeadingAttr + "]");
				this.toggleContentEls = document.querySelectorAll("[" + this.toggleContentAttr + "]");
				this.itemsStatus = {};

				this.loadSettings();
				this.init();
			}
	
			_createClass( HandyCollapse, 
				[	
					{
						key: "init",
						value: function init() {
							if (this.toggleHeadingEls) {
								this.setListner();
							}
							if (this.toggleContentEls) {
								this.setItem();
							}
							this.refresh();
						}
					}, 
					{
						key: "loadSettings",
						value: function loadSettings() {
							// prepare the object that will keep the panel statuses
							this.itemsStatus = {};

							var cookie = this.getcookie();
							if (cookie[0] == "empty") return;
	
							var cookieData = cookie[1];
							for( var key in cookieData ) {
								var value = cookieData[key];
								if (key == "undefined" || key == null || key == "") {
									//alert(key + " " + value);
								}	
								else {	
									this.itemsStatus[key] = value;
								}	
							}
						}
					}, 
					{
						key: "saveSettings",
						value: function saveSettings(id, expand) {
							// put the new expanding in the object
							this.itemsStatus[id] = expand;
	
							// create an array that will keep the contentid:expanding pairs
							var panelsData = [];
							for (var cid in this.itemsStatus) {
								if (cid == "undefined" || cid == null || cid == "") {
									//alert(cid + " " + this.itemsStatus[cid]);
								}	
								else {	
									panelsData.push(cid+":"+this.itemsStatus[cid]);
								}	
							}	
		
							// set the cookie expiration date 1 year from now
							var today = new Date();
							var expirationDate = new Date(today.getTime() + 1 * 1000 * 60 * 60 * 24);
						
							// write the cookie
							document.cookie = this.cookie_name + "=" + escape(panelsData.join(",")) + ";expires=" + expirationDate.toGMTString();
						}
					}, 
					{
						key: "getSubcookie",
						value: function getSubcookie(cookieValue) {
							var itemsArray = {};
							var cData = cookieValue.split(",");
							// split each key:value pair and put in object
							for (var i=0; i< cData.length; i++)
							{
								var pair = cData[i].split(":");
								itemsArray[pair[0]] = pair[1];
							}
							return itemsArray;
						}	
					}, 
					{
						key: "getAllcookies",
						value: function getAllcookies() {
							var c = [];
							if (document.cookie && document.cookie != '') {
								var split = document.cookie.split(';');
								for (var i = 0; i < split.length; i++) {
									var name_value = split[i].split("=");
									name_value[0] = name_value[0].replace(/^ /, '');
									
									var name = decodeURIComponent(name_value[0]);
									var value = this.getSubcookie(decodeURIComponent(name_value[1]));
									
									c.push([name, value]);
								}
							}
							else {
								c = { "empty": "cookie" };
							}	
							return c;
						}
					},	
					{
						key: "getcookie",
						value: function getcookie() {
							var c = this.getAllcookies();
							
							var cr = { "empty": "cookie" };
							for (var i = 0; i < c.length; i++) {

								//alert(c[i][0]);
								if (c[i][0] == this.cookie_name) { 
									cr = [ c[i][0], c[i][1] ];
									break;
								}	
								
							}
							return cr;
						}
					},	
					{
						key: "setListner",
						value: function setListner() {
							var _this2 = this;
							Array.prototype.slice.call(this.toggleHeadingEls).forEach(function(buttonEl, index) {
								var id = buttonEl.getAttribute(_this2.toggleHeadingAttr);
								if (id) {
									buttonEl.addEventListener("click", function(e) {
										e.preventDefault();
										_this2.toggleSlide(id, buttonEl);
									}, false);
								}
							});
						}
					}, 
					{
						key: "setItem",
						value: function setItem() {
							var _this = this;
							Array.prototype.slice.call(this.toggleContentEls).forEach(function(contentEl) {
								contentEl.style.maxHeight = "none";
							});
						}
					}, 
					{
						key: "setItemStatus",
						value: function setItemStatus(id, collapsed) {
							this.itemsStatus[id] = collapsed;
						}
					}, 
					{
						key: "expandAll",
						value: function expandAll() {
							for (var id in this.itemsStatus)
								this.setItemStatus(id, HANDY_EXPAND_CLASS);
		
							this.saveSettings();
							this.refresh();
						}
					}, 
					{
						key: "collapseAll",
						value: function collapseAll() {
							for (var id in this.itemsStatus)
								this.setItemStatus(id, HANDY_COLLAPSED_CLASS);
		
							this.saveSettings();
							this.refresh();
						}
					}, 
					{
						key: "refresh",
						value: function refresh() {
							var _this2 = this;
							Array.prototype.slice.call(this.toggleHeadingEls).forEach(function(buttonEl, index) {
								var id = buttonEl.getAttribute(_this2.toggleHeadingAttr);
								if (id) {
									// look for the id in loaded settings, apply the normal/collapsed class
									if (_this2.itemsStatus.hasOwnProperty(id)) {
										if (_this2.itemsStatus[id] == HANDY_EXPAND_CLASS) {
											buttonEl.parentNode.classList.remove(HANDY_COLLAPSED_CLASS);
											buttonEl.parentNode.classList.add(HANDY_EXPAND_CLASS);
										}	
										else {
											buttonEl.parentNode.classList.remove(HANDY_EXPAND_CLASS);
											buttonEl.parentNode.classList.add(HANDY_COLLAPSED_CLASS);
										}	
									}	
									else {
										// if no saved setting, see the initial setting
										_this2.itemsStatus[id] = 
											(buttonEl.parentNode.classList.contains(HANDY_EXPAND_CLASS)) ? HANDY_EXPAND_CLASS : HANDY_COLLAPSED_CLASS;
									}
						
									var expand = buttonEl.parentNode.classList.contains(HANDY_EXPAND_CLASS);

									_this2.setItemStatus(id, expand ? HANDY_EXPAND_CLASS : HANDY_COLLAPSED_CLASS);
								
									if (expand) {
										_this2.open(id, false, false);
									} else {
										_this2.close(id, false, false);
									}
								}
							});
						}
					}, 
					{
						key: "toggleSlide",
						value: function toggleSlide(id, buttonEl) {
							var isRunCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
	
							if (this.itemsStatus[id] == HANDY_EXPAND_CLASS) {
								this.saveSettings(id, HANDY_COLLAPSED_CLASS);
								this.close(id, isRunCallback, this.isAimation);
							} else {
								this.saveSettings(id, HANDY_EXPAND_CLASS);
								this.open(id, isRunCallback, this.isAimation);
							}
						}
					}, 
					{
						key: "open",
						value: function open(id) {
							var _this3 = this;
							var isRunCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
							var isAimation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
							if (!id) return;
							if (this.closeOthers) {
								Array.prototype.slice.call(this.toggleHeadingEls).forEach(function(buttonEl, index) {
									var closeId = buttonEl.getAttribute(_this3.toggleHeadingAttr);
									if (closeId !== id) _this3.close(closeId, false, isAimation);
								});
							}
							if (isRunCallback !== false) this.onSlideStart(true, id);
							var toggleButton = document.querySelector("[" + this.toggleHeadingAttr + "='" + id + "']");
							var toggleContent = document.querySelector("[" + this.toggleContentAttr + "='" + id + "']");
							var clientHeight = this.getTargetHeight(toggleContent);
							toggleButton.parentNode.classList.remove(HANDY_COLLAPSED_CLASS);
							toggleButton.parentNode.classList.add(HANDY_EXPAND_CLASS);
							if (isAimation) {
								toggleContent.style.transition = this.animatinSpeed + "ms " + this.cssEasing;
								toggleContent.style.maxHeight = (clientHeight || "1000") + "px";
								setTimeout(function() {
									if (isRunCallback !== false) _this3.onSlideEnd(true, id);
									toggleContent.style.maxHeight = "none";
									toggleContent.style.transition = "";
								}, this.animatinSpeed);
							} else {
								toggleContent.style.maxHeight = "none";
							}
							this.itemsStatus[id] = HANDY_EXPAND_CLASS;
						}	
					}, 
					{
						key: "close",
						value: function close(id) {
							var _this4 = this;
							var isRunCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
							var isAimation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
							if (!id) return;
							if (isRunCallback !== false) this.onSlideStart(false, id);
							var toggleButton = document.querySelector("[" + this.toggleHeadingAttr + "='" + id + "']");
							var toggleContent = document.querySelector("[" + this.toggleContentAttr + "='" + id + "']");
							toggleButton.parentNode.classList.remove(HANDY_EXPAND_CLASS);
							toggleButton.parentNode.classList.add(HANDY_COLLAPSED_CLASS);
							toggleContent.style.maxHeight = toggleContent.clientHeight + "px";
							setTimeout(function() {
								toggleContent.style.maxHeight = "0px";
							}, 5);
							if (isAimation) {
								toggleContent.style.transition = this.animatinSpeed + "ms " + this.cssEasing;
								setTimeout(function() {
									if (isRunCallback !== false) _this4.onSlideEnd(false, id);
									toggleContent.style.transition = "";
								}, this.animatinSpeed);
							} else {
								this.onSlideEnd(false, id);
							}
							this.itemsStatus[id] = HANDY_COLLAPSED_CLASS;
						}
					}, 
					{
						key: "getTargetHeight",
						value: function getTargetHeight(targetEl) {
							if (!targetEl) return;
							var cloneEl = targetEl.cloneNode(true);
							var parentEl = targetEl.parentNode;
							cloneEl.style.maxHeight = "none";
							cloneEl.style.opacity = "0";
							parentEl.appendChild(cloneEl);
							targetEl.style.display = "block";
							var clientHeight = cloneEl.clientHeight;
							parentEl.removeChild(cloneEl);
							return clientHeight;
						}
					}
				]
			);
    
			return HandyCollapse;
		}();

		if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === 'object') {
			module.exports = HandyCollapse;
		}
	</script>
	<script type="text/javascript">
        //Nested
        let handy_nested = new HandyCollapse({
            nameSpace: "nested",
            closeOthers: false
        });
	</script>

	<script type="text/javascript">
	</script>

	<script type="text/javascript">
		function formatJSON(jsonstr, html = false) {
			var tabcount = 0;
			var result = "";
			var inquote = false;
			var ignorenext = false;
			var tab = " ";
			var newline = "\n";
			if (html) {
				tab = "  ";
				newline = "<br/>";
			}
			for(var i = 0; i < jsonstr.length; i++) 
			{
				c = jsonstr[i];
				if (ignorenext) {
					result += c;
					ignorenext = false;
				} else {
					switch(c) {
						case '[':
						case '{':
							tabcount++;
							result += c + newline + tab.repeat( tabcount*2);
							break;
						case ']':
						case '}':
							tabcount--;
							result = result.trim() + newline + tab.repeat( tabcount*2) + c;
							break;
						case ',':
							result += c + newline + tab.repeat( tabcount*2);
							break;
						case '"':
							if ( !inquote ) {	
								if ( jsonstr[i-1] == ':' || jsonstr[i-2] == ':' )
									result += '<span style="color:#0000ff;">';
								else
									result += '<span style="color:#ff0000;">';
							}
							if (inquote)
								result += '</span>';
							inquote = !inquote;
							result += c;
							break;
						case '\\':
							if (inquote) ignorenext = true;
							result += c;
							break;
						default:
							result += c;
					}
				}
			}
			if (result == "")
				result = "no data...";
			else
				result = "<pre>"+result+"</pre>";

			return result;
		}
		
		var json_data1 = {a:1, 'b':'foo', c:[false, 'false', null, 'null', {d:{e:1.3e5, f:'1.3e5'}}]};
		var json_data2 = { "data": { "x": 1, "y": 1, "url": "http://url.com" }, "event": "start", "show": 1, "id": 50 };	
		var json_data = {
			key0: "<script>alert('no xss!')<\/script>",
			key1: "alert('no xss!')",
			key2: 12345,
			key3: new Date(),
			key4: [],
			key5: [
				123,
				"123",
				{
					a: 5,
					b: 6,
					c: null,
					d: true
				}
			],
			key6: {
				a: 1,
				b: 3,
				c: {
					d: 4
				}
			}
		};

		function all_cookiesArray() 
		{
			function getSubcookie(cookieValue) 
			{
				var itemsArray = {};
				var cData = cookieValue.split(",");
				// split each key:value pair and put in object
				for (var i=0; i< cData.length; i++)
				{
					var pair = cData[i].split(":");
					itemsArray[pair[0]] = pair[1];
				}
				return itemsArray;
			}
			var c = [];
			if (document.cookie && document.cookie != '') {
				var split = document.cookie.split(';');
				for (var i = 0; i < split.length; i++) {
					var name_value = split[i].split("=");
					name_value[0] = name_value[0].replace(/^ /, '');
					c.push(decodeURIComponent(name_value[0]), getSubcookie(decodeURIComponent(name_value[1])));
				}
			}
			else {
				c = { "empty": "cookie" };
			}	
			return c;
		}

		function loadjsondata() {
			var json_data_str = JSON.stringify(json_data);
			document.getElementById("disp_format_json").innerHTML  = "json_data<br>"+formatJSON(json_data_str);
			var json_data_str1 = JSON.stringify(json_data1);
			document.getElementById("disp_format_json1").innerHTML  = "json_data1<br>"+formatJSON(json_data_str1);
			var json_data_str2 = JSON.stringify(json_data2);
			document.getElementById("disp_format_json2").innerHTML  = "json_data2<br>"+formatJSON(json_data_str2);

			var handycookiesArray = handy_nested.getcookie();
			var handycookiesArray_str = JSON.stringify(handycookiesArray);
			document.getElementById("handycookiesArray").innerHTML  = "handycookiesArray<br>"+formatJSON(handycookiesArray_str);

			var allcookiesArray = all_cookiesArray();
			var allcookiesArray_str = JSON.stringify(allcookiesArray);
			document.getElementById("allcookiesArray").innerHTML  = "allcookiesArray<br>"+formatJSON(allcookiesArray_str);
		}

		loadjsondata();
	</script>

	<script type="text/javascript">
		document.getElementById("loadjsondata").addEventListener('click', function(event)
		{
			event.preventDefault();
			loadjsondata();
			return false;
		} );
	</script>

	</body>
</html>

 

첨부파일 handy-nested-panel.zip (5.9 KB)
 이전글 Javascript animated collapsibl... (1)

댓글

darkninja / 2023/03/06 21:16:15 / 추천 0

지금 실력으로는 이것이 최선의 결과물입니다;

원소스의 'data-nested-control' => 'data-nested-button' 으로 바뀌었습니다.

쿠키 저장과 불러오는 부분이 수정되었습니다

쿠키저장 데이타의 형식은 확장성과 가독성을 봤을때 이 방법이 무난해 보입니다.

더 나은 방법이 있다면 알려주세요. 


<!DOCTYPE html>

<html>

<head>
	<meta charset="utf-8" />
	<title>Test page</title>

	<style>
		pre
		{
			padding: 0px;
			border-left: 3px solid #ccc;
			border-right: 2px solid #ccc;
			margin: 0px;
			overflow: auto;	width: 99.7%;
			background-color: #F5F5F5;
		}
	</style>

	<style>
/* handy-collapse */
/* top, right, bottom, left */

.handy-panel
{
	background: #eee;
	margin: 1px;
	border: 1px solid #CCC;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
}

.handy-subpanel
{ 
	background: #eee;
	margin: 1px 10px 1px 10px;
	border: 1px solid #CCC;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
}

.handy-panel.collapsed,
.handy-subpanel.collapsed
{
	border: 1px solid #999;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
}

.handy-panel.expand,
.handy-subpanel.expand
{
	border-left: 1px solid #CCC;
	border-right: 1px solid #CCC;
	border-bottom: 1px solid #CCC;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
}

.handy-panel > button,
.handy-subpanel > button
{
	margin: 0px;
	padding: 2px 2px 2px 5px;
	background: #CCC url(images/arrow-up.gif) no-repeat 99.2% center;
	border: 0px;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
}

.handy-panel > button:hover,
.handy-subpanel > button:hover { 
	background-color: #A9BCEF; 
	cursor:pointer;
}

.handy-panel.collapsed > button,
.handy-subpanel.collapsed > button
{
	margin: 0px;
	padding: 2px 2px 2px 5px;
	background: #CCC url(images/arrow-dn.gif) no-repeat 99.2% center;
	border-color: #CCC;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
}

.handy-panel.collapsed > button:hover,
.handy-subpanel.collapsed > button:hover
{
	margin: 0px;
	padding: 2px 2px 2px 5px;
	background: #A9BCEF url(images/arrow-dn.gif) no-repeat 99.2% center;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
}

.handy-panelcontent
{ 
	padding: 2px 3px 2px 4px; 
	margin: 0px 0px 0px 0px;
	overflow: hidden;
	list-style-type: none;
}

.handy-panelcontent.is-1 { background-color:#FFFFFF; }
.handy-panelcontent.is-2 { background-color:#F7F7F7; }
.handy-panelcontent.is-3 { background-color:#EEEEEE; }
.handy-panelcontent.is-4 { background-color:#E7E7E7; }
.handy-panelcontent.is-5 { background-color:#CACACA; }


.handy-panelcontent p
{ 
	margin: 0px; 
	padding: 0px 0px 2px 0px; 
}

/* collapsed panel content */
.handy-panel.collapsed .handy-panelcontent { display: none; }
.handy-subpanel.collapsed .handy-panelcontent { display: none; }

.button.is-fullwidth{
	display:flex;
	width:100%
}
	</style>

</head>

<body>
	<?php
		//source
		//https://www.media-division.com/javascript-animated-collapsible-panels-without-frameworks/
		//https://github.com/sk-rt/handy-collapse
		//https://www.cssscript.com/nested-accordion-content-toggle-handy-collapse/
		//https://www.cssscript.com/demo/Nested-Accordion-Content-Toggle-handy-collapse/
		//https://stackoverflow.com/questions/6054033/pretty-printing-json-with-php
		//https://github.com/LorDOniX/json-viewer
		//https://github.com/JAAulde/cookies
	?>
	
	<div class="handy-panel left expand">
		<button type="button" class="button is-fullwidth" data-nested-button="handy-panel-nested">handy-panel-nested</button>
		<div class="handy-panelcontent is-1" data-nested-content="handy-panel-nested">
			<button type="button" id="loadjsondata">load jsondata</button>
		
			<div class="handy-panel left">
				<button type="button" class="button is-fullwidth" data-nested-button="handy-panel">handy-panel</button>
				<div class="handy-panelcontent is-1" data-nested-content="handy-panel">
					<div id="handycookiesArray" name="handycookiesArray"></div>
					<div id="allcookiesArray" name="allcookiesArray"></div>
				</div>
			</div>
			
			<div class="handy-panel left">
				<button type="button" class="button is-fullwidth" data-nested-button="jsonformatview">jsonformatview</button>
				<div class="handy-panelcontent is-1" data-nested-content="jsonformatview">
					<div id="disp_format_json" name="disp_format_json">0</div>
					<div id="disp_format_json1" name="disp_format_json1">1</div>
					<div id="disp_format_json2" name="disp_format_json2">2</div>
				</div>
			</div>

		</div>
	</div>

	<script type="text/javascript">
var HANDY_PANEL           = "handy-panel";
var HANDY_SUBPANEL        = "handy-subpanel";
var HANDY_PANELCONTENT    = "handy-panelcontent";

var HANDY_NAMESPACE_HEAD  = "data"
var HANDY_NAMESPACE       = "basic"
var HANDY_HEADING_TAG     = "button";
var HANDY_CONTENT_TAG     = "content";

var HANDY_EXPAND_CLASS    = "expand";
var HANDY_COLLAPSED_CLASS = "collapsed";

var HANDY_COOKIE_NAME     = "handy-panels";
var HANDY_CSS_EASING      = "ease-in-out";

var HANDY_ANIMATION_DELAY = 400; /*ms*/
var HANDY_ANIMATION_STEPS = 10;

var HANDY_PANEL_LEFT      = "left";
var HANDY_PANEL_RIGHT     = "right";

var _typeof = 
	typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? 
		function(obj) { return typeof obj; } : 
		function(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

var _extends = Object.assign || function(target) 
{
    for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];
        for (var key in source) {
            if (Object.prototype.hasOwnProperty.call(source, key)) {
                target[key] = source[key];
            }
        }
    }
    return target;
};

var _createClass = function() 
{
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
			
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
			
            if ("value" in descriptor) descriptor.writable = true;
			
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }
    return function(Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps);
		
        return Constructor;
    };
}();
 
function _classCallCheck(instance, Constructor) 
{
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

function getHandyPanelsHeight()
{
	var panelsList = document.getElementsByClassName(HANDY_PANEL);
	var panelsleft = new Array();
	var panelsright = new Array();
	var lh = 0;
	var rh = 0;
	var panel;

	for (var i=0; i<panelsList.length; i++)
	{
		panel = panelsList[i];
		if (panel.classList.contains(HANDY_PANEL_LEFT)) {
			lh += panel.offsetHeight;
		}	
		else if (panel.classList.contains(HANDY_PANEL_RIGHT)) {
			rh += panel.offsetHeight;
		}	
	}
	
	var lsw = document.getElementById('left_sidebar_wrapper');
	var lsw_h = window.getComputedStyle(lsw).height;
	lsw_h = Number(lsw_h.replace('px', ''));

	var rsw = document.getElementById('right_sidebar_wrapper');
	var rsw_h = window.getComputedStyle(rsw).height;
	rsw_h = Number(rsw_h.replace('px', ''));

	var big_lr = (lh>rh) ? lh : rh;
	var big_lswrsw = (lsw_h>rsw_h) ? lsw_h : rsw_h;
	var big = (big_lr>big_lswrsw) ? big_lr : big_lswrsw;

    var ret = [];
	ret['lh'] = lh;
	ret['rh'] = rh;
	ret['lsw_h'] = lsw_h;
	ret['rsw_h'] = rsw_h;
	ret['big_lr'] = big_lr;
	ret['big_lswrsw'] = big_lswrsw;
	ret['big'] = big;

	return ret;
}

var HandyCollapse = function() 
{
    function HandyCollapse() 
	{
        var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
		
        _classCallCheck(this, HandyCollapse);
		
        _extends(this, {
            nameSpace: HANDY_NAMESPACE,
            toggleHeadingAttr: HANDY_NAMESPACE_HEAD + "-" + (options.nameSpace || HANDY_NAMESPACE) + "-" + HANDY_HEADING_TAG,
            toggleContentAttr: HANDY_NAMESPACE_HEAD + "-" + (options.nameSpace || HANDY_NAMESPACE) + "-" + HANDY_CONTENT_TAG,
            isAimation: true,
            closeOthers: true,
            animatinSpeed: HANDY_ANIMATION_DELAY,
            cssEasing: HANDY_CSS_EASING,
			cookie_name : HANDY_COOKIE_NAME + "-" + options.nameSpace,
            onSlideStart: function onSlideStart() {
                return false;
            },
            onSlideEnd: function onSlideEnd() {
                return false;
            }
        }, options);
		
        this.toggleHeadingEls = document.querySelectorAll("[" + this.toggleHeadingAttr + "]");
        this.toggleContentEls = document.querySelectorAll("[" + this.toggleContentAttr + "]");
        this.itemsStatus = {};

		this.loadSettings();
        this.init();
    }
	
    _createClass( HandyCollapse, 
		[	
			{
				key: "init",
				value: function init() {
					if (this.toggleHeadingEls) {
						this.setListner();
					}
					if (this.toggleContentEls) {
						this.setItem();
					}
					this.refresh();
				}
			}, 
			{
				key: "loadSettings",
				value: function loadSettings() {
					// prepare the object that will keep the panel statuses
					this.itemsStatus = {};
					var s, state = this.getcookie();	
					if (state) {
                        try {
							s = JSON.parse(state);	
                        } catch (e1) {
                            s = null;
                        }
						if (s) {
							var sp = s.panelsData;		
							for (var i = 0; i < sp.length; i++) {
								var key = sp[i][0];
								var value = sp[i][1];
								if (key == "undefined" || key == null || key == "") {
									//alert(key + " " + value);
								}	
								else {	
									this.itemsStatus[key] = value;
								}	
							}	
						}
					}	
				}
			}, 
			{
				key: "saveSettings",
				value: function saveSettings(id, expand) {
					// put the new expanding in the object
					this.itemsStatus[id] = expand;
	
					// create an array that will keep the contentid:expanding pairs
					var panelsData = [];
					for (var cid in this.itemsStatus) {
						if (cid == "undefined" || cid == null || cid == "") {
							//alert(cid + " " + this.itemsStatus[cid]);
						}	
						else {	
							panelsData.push([cid, this.itemsStatus[cid]]);
						}	
					}	

					var state_json = {
						panelsData : panelsData
					};

					var state = JSON.stringify(state_json);
		
					// set the cookie expiration date 1 year from now
					var today = new Date();
					var expirationDate = new Date(today.getTime() + 1 * 1000 * 60 * 60 * 24);
					
					// write the cookie
					document.cookie = this.cookie_name + "=" + encodeURIComponent(state) + ";expires=" + expirationDate.toGMTString();
				}
			}, 
			{
				key: "getAllcookies",
				value: function getAllcookies() {
					var c = [];
					if (document.cookie && document.cookie != '') {
						var split = document.cookie.split(';');
						for (var i = 0; i < split.length; i++) {
							var name_value = split[i].split("=");
							name_value[0] = name_value[0].replace(/^ /, '');
								
							var name = decodeURIComponent(name_value[0]);
							var value = decodeURIComponent(name_value[1]);

							c[name] = value;
						}
					}
					return c;
				}
			},	
			{
				key: "getcookie",
				value: function getcookie() {
					var c = this.getAllcookies();
					var cr = [];
					for (var key in c) {
						if (key == this.cookie_name) { 
							cr = c[key];
							break;
						}	
					}
					return cr;
				}
			},	
			{
				key: "setListner",
				value: function setListner() {
					var _this2 = this;
					Array.prototype.slice.call(this.toggleHeadingEls).forEach(function(buttonEl, index) {
						var id = buttonEl.getAttribute(_this2.toggleHeadingAttr);
						if (id) {
							buttonEl.addEventListener("click", function(e) {
								e.preventDefault();
								_this2.toggleSlide(id, buttonEl);
							}, false);
						}
					});
				}
			}, 
			{
				key: "setItem",
				value: function setItem() {
					var _this = this;
					Array.prototype.slice.call(this.toggleContentEls).forEach(function(contentEl) {
						contentEl.style.maxHeight = "none";
					});
				}
			}, 
			{
				key: "setItemStatus",
				value: function setItemStatus(id, collapsed) {
					this.itemsStatus[id] = collapsed;
				}
			}, 
			{
				key: "expandAll",
				value: function expandAll() {
					for (var id in this.itemsStatus)
						this.setItemStatus(id, HANDY_EXPAND_CLASS);
		
					this.saveSettings();
					this.refresh();
				}
			}, 
			{
				key: "collapseAll",
				value: function collapseAll() {
					for (var id in this.itemsStatus)
						this.setItemStatus(id, HANDY_COLLAPSED_CLASS);
		
					this.saveSettings();
					this.refresh();
				}
			}, 
			{
				key: "refresh",
				value: function refresh() {
					var _this2 = this;
					Array.prototype.slice.call(this.toggleHeadingEls).forEach(function(buttonEl, index) {
						var id = buttonEl.getAttribute(_this2.toggleHeadingAttr);
						if (id) {

							// look for the id in loaded settings, apply the normal/collapsed class
							if (_this2.itemsStatus.hasOwnProperty(id)) {
								if (_this2.itemsStatus[id] == HANDY_EXPAND_CLASS) {
									buttonEl.parentNode.classList.remove(HANDY_COLLAPSED_CLASS);
									buttonEl.parentNode.classList.add(HANDY_EXPAND_CLASS);
								}	
								else {
									buttonEl.parentNode.classList.remove(HANDY_EXPAND_CLASS);
									buttonEl.parentNode.classList.add(HANDY_COLLAPSED_CLASS);
								}	
							}	
							else {
								// if no saved setting, see the initial setting
								_this2.itemsStatus[id] = 
									(buttonEl.parentNode.classList.contains(HANDY_EXPAND_CLASS)) ? HANDY_EXPAND_CLASS : HANDY_COLLAPSED_CLASS;
							}
						
							var expand = buttonEl.parentNode.classList.contains(HANDY_EXPAND_CLASS);

							_this2.setItemStatus(id, expand ? HANDY_EXPAND_CLASS : HANDY_COLLAPSED_CLASS);
							
							if (expand) {
								_this2.open(id, false, false);
							} else {
								_this2.close(id, false, false);
							}
						}
					});
				}
			}, 
			{
				key: "toggleSlide",
				value: function toggleSlide(id, buttonEl) {
					var isRunCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

					if (this.itemsStatus[id] == HANDY_EXPAND_CLASS) {
						this.saveSettings(id, HANDY_COLLAPSED_CLASS);
						this.close(id, isRunCallback, this.isAimation);
					} else {
						this.saveSettings(id, HANDY_EXPAND_CLASS);
						this.open(id, isRunCallback, this.isAimation);
					}
				}
			}, 
			{
				key: "open",
				value: function open(id) {
					var _this3 = this;
					var isRunCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
					var isAimation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
					if (!id) return;
					if (this.closeOthers) {
						Array.prototype.slice.call(this.toggleHeadingEls).forEach(function(buttonEl, index) {
							var closeId = buttonEl.getAttribute(_this3.toggleHeadingAttr);
							if (closeId !== id) _this3.close(closeId, false, isAimation);
						});
					}
					if (isRunCallback !== false) this.onSlideStart(true, id);
					var toggleButton = document.querySelector("[" + this.toggleHeadingAttr + "='" + id + "']");
					var toggleContent = document.querySelector("[" + this.toggleContentAttr + "='" + id + "']");
					var clientHeight = this.getTargetHeight(toggleContent);
					toggleButton.parentNode.classList.remove(HANDY_COLLAPSED_CLASS);
					toggleButton.parentNode.classList.add(HANDY_EXPAND_CLASS);
					if (isAimation) {
						toggleContent.style.transition = this.animatinSpeed + "ms " + this.cssEasing;
						toggleContent.style.maxHeight = (clientHeight || "1000") + "px";
						setTimeout(function() {
							if (isRunCallback !== false) _this3.onSlideEnd(true, id);
							toggleContent.style.maxHeight = "none";
							toggleContent.style.transition = "";
						}, this.animatinSpeed);
					} else {
						toggleContent.style.maxHeight = "none";
					}
					this.itemsStatus[id] = HANDY_EXPAND_CLASS;
				}
			}, 
			{
				key: "close",
				value: function close(id) {
					var _this4 = this;
					var isRunCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
					var isAimation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
					if (!id) return;
					if (isRunCallback !== false) this.onSlideStart(false, id);
					var toggleButton = document.querySelector("[" + this.toggleHeadingAttr + "='" + id + "']");
					var toggleContent = document.querySelector("[" + this.toggleContentAttr + "='" + id + "']");
					toggleButton.parentNode.classList.remove(HANDY_EXPAND_CLASS);
					toggleButton.parentNode.classList.add(HANDY_COLLAPSED_CLASS);
					toggleContent.style.maxHeight = toggleContent.clientHeight + "px";
					setTimeout(function() {
						toggleContent.style.maxHeight = "0px";
					}, 5);
					if (isAimation) {
						toggleContent.style.transition = this.animatinSpeed + "ms " + this.cssEasing;
						setTimeout(function() {
							if (isRunCallback !== false) _this4.onSlideEnd(false, id);
							toggleContent.style.transition = "";
						}, this.animatinSpeed);
					} else {
						this.onSlideEnd(false, id);
					}
					this.itemsStatus[id] = HANDY_COLLAPSED_CLASS;
				}
			}, 
			{
				key: "getTargetHeight",
				value: function getTargetHeight(targetEl) {
					if (!targetEl) return;
					var cloneEl = targetEl.cloneNode(true);
					var parentEl = targetEl.parentNode;
					cloneEl.style.maxHeight = "none";
					cloneEl.style.opacity = "0";
					parentEl.appendChild(cloneEl);
					targetEl.style.display = "block";
					var clientHeight = cloneEl.clientHeight;
					parentEl.removeChild(cloneEl);
					return clientHeight;
				}
			}
		]
	);
    
	return HandyCollapse;
}();

if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === 'object') {
    module.exports = HandyCollapse;
}
	</script>
	<script type="text/javascript">
        //Nested
        let handy_nested = new HandyCollapse({
            nameSpace: "nested",
            closeOthers: false
        });
	</script>

	<script type="text/javascript">
	</script>

	<script type="text/javascript">
		function formatJSON(jsonstr, html = false) {
			var tabcount = 0;
			var result = "<pre>";
			var inquote = false;
			var ignorenext = false;
			var tab = " ";
			var newline = "\n";
			if (html) {
				tab = "    ";
				newline = "<br/>";
			}
			
			//alert(jsonstr);
			
			for(var i = 0; i < jsonstr.length; i++) 
			{
				c = jsonstr[i];
				if (ignorenext) {
					result += c;
					ignorenext = false;
				} else {
					switch(c) {
						case '[':
						case '{':
							tabcount++;
							result += c + newline + tab.repeat( tabcount*2);
							break;
						case ']':
						case '}':
							tabcount--;
							result = result.trim() + newline + tab.repeat( tabcount*2) + c;
							break;
						case ',':
							result += c + newline + tab.repeat( tabcount*2);
							break;
						case '"':
							if ( !inquote ) {	
								if ( jsonstr[i-1] == ':' || jsonstr[i-2] == ':' )
									result += '<span style="color:#0000ff;">';
								else
									result += '<span style="color:#ff0000;">';
							}
							if (inquote)
								result += '</span>';
							inquote = !inquote;
							result += c;
							break;
						case '\\':
							if (inquote) ignorenext = true;
							result += c;
							break;
						default:
							result += c;
					}
				}
			}
			result += "</pre>";
			return result;
		}
		
		var json_data1 = {a:1, 'b':'foo', c:[false, 'false', null, 'null', {d:{e:1.3e5, f:'1.3e5'}}]};
		var json_data2 = { "data": { "x": 1, "y": 1, "url": "http://url.com" }, "event": "start", "show": 1, "id": 50 };	
		var json_data = {
			key0: "<script>alert('no xss!')<\/script>",
			key1: "alert('no xss!')",
			key2: 12345,
			key3: new Date(),
			key4: [],
			key5: [
				123,
				"123",
				{
					a: 5,
					b: 6,
					c: null,
					d: true
				}
			],
			key6: {
				a: 1,
				b: 3,
				c: {
					d: 4
				}
			}
		};

		function all_cookiesArray() 
		{
			var c = [];
			if (document.cookie && document.cookie != '') {
				var split = document.cookie.split(';');
				for (var i = 0; i < split.length; i++) {
					var name_value = split[i].split("=");
					name_value[0] = name_value[0].replace(/^ /, '');
						
					c.push([decodeURIComponent(name_value[0]), decodeURIComponent(name_value[1])]);
				}
			}
			return c;
		}

		function loadjsondata() {
			var json_data_str = JSON.stringify(json_data);
			document.getElementById("disp_format_json").innerHTML  = "json_data<br>"+formatJSON(json_data_str);
			var json_data_str1 = JSON.stringify(json_data1);
			document.getElementById("disp_format_json1").innerHTML  = "json_data1<br>"+formatJSON(json_data_str1);
			var json_data_str2 = JSON.stringify(json_data2);
			document.getElementById("disp_format_json2").innerHTML  = "json_data2<br>"+formatJSON(json_data_str2);

			var handycookiesArray = ["handy_nested", handy_nested.getcookie()];
			var handycookiesArray_str = JSON.stringify(handycookiesArray);
			handycookiesArray_str = handycookiesArray_str.replaceAll('\\', '');
			document.getElementById("handycookiesArray").innerHTML  = "handycookiesArray<br>"+formatJSON(handycookiesArray_str);

			var cookiesgetAll = all_cookiesArray();
			var cookiescookiesgetAllstr = JSON.stringify(cookiesgetAll);
			cookiescookiesgetAllstr = cookiescookiesgetAllstr.replaceAll('\\', '');
			document.getElementById("allcookiesArray").innerHTML  = "disp format cookies<br>"+formatJSON(cookiescookiesgetAllstr);
		}

		loadjsondata();
	</script>

	<script type="text/javascript">
		document.getElementById("loadjsondata").addEventListener('click', function(event)
		{
			event.preventDefault();
			loadjsondata();
			return false;
		} );
	</script>

	</body>
</html>