Link to home
Start Free TrialLog in
Avatar of Howard Bash
Howard BashFlag for United States of America

asked on

Expanding/Collapsing Tree on SharePoint List

I am taking a list item that has a JSON object stored as a string.  I want to display the list item and that JSON object in some kind of tree view with expand/collapse without going too crazy.

Any ideas about this would be greatly appreciated
Avatar of Leonidas Dosas
Leonidas Dosas
Flag of Greece image

Check this plugin.It provides interactive trees jsTree
try jqTree

https://mbraak.github.io/jqTree/

JqTree is a jQuery widget for displaying a tree structure in html. It supports json data, loading via ajax and drag-and-drop.

Features

  • Create a tree from JSON data
  • Load data using ajax
  • Drag and drop
  • Saves the state
  • Keyboard support
  • Lazy loading
  • Works on ie9+, firefox, chrome and safari
  • Written in Typescript
Avatar of Howard Bash

ASKER

This plugin is nice.  The only issue is that is has some requirements for the JSON structure which contains the data it renders.  To use this,  I would have to write some loops to go through the real data JSON object and build out the object as the plugin needs.  Otherwise, it's a nice option.
If you want give us a sample data JSON to show you an example for each of them
{
	"layer": [
		{
			"number": "1",
			"name": "Layer Name One",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		},
		{
			"number": "2",
			"name": "Layer Name Two",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		},
		{
			"number": "3",
			"name": "Layer Name Three",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		}
	]
}

Open in new window

<!doctype html>
    <html lang="en">
    
    <head>
        <title>JStree</title>
        <meta charset="utf-8" />


          <!-- JavaScript functions will go here -->
          <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
        
    </head>
    
<body>
    <div id="jstree">

    
    </div>
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
        <script>
var data={
	"layer": [            
		{
                        "state"       : {
                        "opened"    : true,
                        "selected" : true},                        
                        "text":"This is data one",                        
			"number": "1",
			"name": "Layer Name One",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]                    
		},
		{
                    
                        "state"       : {
                        "opened"    : true,
                        "selected" : true},
                        "text":"This is data two",
			"number": "2",
			"name": "Layer Name Two",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		},
		{
                    
                        "state"       : {
                        "opened"    : true,
                        "selected" : true},
                        "text":"This is data three",
			"number": "3",
			"name": "Layer Name Three",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		}
	]
};


    $(function () {
        $('#jstree').jstree({'core':{
         'data':data.layer 
            }});        
    });  



        </script>
    
    

    </body>
    
    </html>

Open in new window

Nice.  But it still looks like I have to build these structures after I obtain the JSON strings.
I tried the data and no tree.  It runs, but no children under the layers.  Here is  a screen shot.
User generated image
Are you want to expand the JSON data further more?
None of the data below "This is data one..." is viewable.  There is no expansion from what you see.  The other data you show in your example isn't viewable and needs to be.
Here is the look with hardcoded data that matches the plugin examples:
User generated image
OK I understand now...Check this lightweight plugin  json-viewer.
Download first.Add the file in your project.
Add in your document head section first JQuery then json-viewer.css and at last json-viewer.js. You can see my code below.Note that src attributes in the link and script elements is for my project so it should be different at yours:
<!doctype html>
    <html lang="en">
    
    <head>
        <title>JStree</title>
        <meta charset="utf-8" />

          <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
          <!-- JavaScript functions will go here -->
          <link href="jquery.json-viewer-master/json-viewer/jquery.json-viewer.css" rel="stylesheet" type="text/css"/>
          <script src="jquery.json-viewer-master/json-viewer/jquery.json-viewer.js" type="text/javascript"></script>
    </head>
    
<body>
 <pre id="json-renderer"></pre>
 


        <script>
var data={
	"layer": [            
		{
                                               
			"number": "1",
			"name": "Layer Name One",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]                    
		},
		{
                    
			"number": "2",
			"name": "Layer Name Two",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		},
		{
                  
			"number": "3",
			"name": "Layer Name Three",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		}
	]
};

$('#json-renderer').jsonViewer(data);



        </script>
    
    

    </body>
    
    </html>

Open in new window


And the final result...
User generated image
Honestly,  the jtree plugin renders an nicer looking tree.  I just may have to port the JSON data to the structure it supports.
So,  now I'm building the function to build the jtree structure and find that I am missing something basic in my JavaScript.  I am pushing elements into an array and when I examine the array,  I have all dups.  During the debug the elements are changing, but the result is n elements with the value of the last pushed element.  

Not sure what I'm bone heading on.  Here is the code:

		var newTemplate = {
		"Id": "",
		"parent": "",
		"text": ""
		};


		function BuildJSONStructure (jObjS) {
			var NewJSObj = newTemplate;
			var retObj = [];
			var alpha= ['a','b','c','d','e','f','g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'];
			var alphaPtr;
			jObj = JSON.parse(jObjS.layer);
			for (var lPtr=0; lPtr<jObj.layer.length; lPtr++)
			{	
				NewJSObj = newTemplate;
				alphaPtr = 0;
				
				//Layer Name
				NewJSObj.Id="ajson"+ String(lPtr+1);
				NewJSObj.parent = "ajson0";
				NewJSObj.text = "Layer" + String(lPtr+1);
				retObj.push(NewJSObj);
				
				//Layer Type
				NewJSObj = newTemplate;
				NewJSObj.Id="ajson" + String(lPtr+1) + alpha[alphaPtr];
				alphaPtr=alphaPtr + 1;
				NewJSObj.parent = "ajson" + String(lPtr);
				NewJSObj.text = jObj.layer[lPtr].type;
				retObj.push(NewJSObj);
				
				//Layer Thickess
				NewJSObj = newTemplate;
				NewJSObj.Id="ajson" + String(lPtr+1) + alpha[alphaPtr];
				alphaPtr=alphaPtr + 1;
				NewJSObj.parent = "ajson" + String(lPtr);
				NewJSObj.text = jObj.layer[lPtr].thickGauge;
				retObj.push(NewJSObj);
				
				//layerPerc
				NewJSObj = newTemplate;
				NewJSObj.Id="ajson" + String(lPtr+1) + alpha[alphaPtr];
				alphaPtr=alphaPtr + 1;
				NewJSObj.parent = "ajson" + String(lPtr);
				NewJSObj.text = jObj.layer[lPtr].layerPerc;
				retObj.push(NewJSObj);
				
				var alphaPtr2 = 0;									
				for (var cPtr=cPtr=0; cPtr<jObj.layer[cPtr].component.length; cPtr++) {					
					//Component Name
					NewJSObj = newTemplate;
					NewJSObj.Id="ajson" + String(lPtr+1) + alpha[alphaPtr] + alpha[alphaPtr2] + String(cPtr + 1);
					alphaPtr2=alphaPtr2 + 1;
					NewJSObj.parent = "ajson" + String(lPtr+1) + alpha[alphaPtr];
					NewJSObj.text = jObj.layer[lPtr].component[cPtr].name;
					retObj.push(NewJSObj);
					
					//Component Percentage
					NewJSObj = newTemplate;
					NewJSObj.Id="ajson" + String(lPtr+1) + alpha[alphaPtr] + alpha[alphaPtr2] + String(cPtr + 1);
					alphaPtr2=alphaPtr2 + 1;
					NewJSObj.parent = "ajson" + String(lPtr+1) + alpha[alphaPtr];
					NewJSObj.text = jObj.layer[lPtr].component[cPtr].percent;
					retObj.push(NewJSObj);
				}				
			}
			
			return JSON.stringify(retObj);
		}

Open in new window

I hope this satisfy your criteria. Note that after 'data' option in the jstree function there is an option named children.In this array I set all the key values pair of the json.Now you can set an icon for each unorder list of items. The I hope to help this. There is an issue that I am thinking. All this bunch of code to do dynamically after an another function check the object lenght.

<!doctype html>
    <html lang="en">
    
    <head>
        <title>JStree</title>
        <meta charset="utf-8" />


          <!-- JavaScript functions will go here -->
          <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
        
    </head>
    
<body>    
    <div id="jstree_1"></div>
    <div id="jstree_2"></div>
    <div id="jstree_3"></div>
    
    
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
        <script>
var data={
	"layer": [            
		{
                                      
			"number": "1",
			"name": "Layer Name One",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]                    
		},
		{
   
			"number": "2",
			"name": "Layer Name Two",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		},
		{
     
			"number": "3",
			"name": "Layer Name Three",
			"type": "Core",
			"thickGauge": "89",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]
		}
	]
}; 

 
$('#jstree_1').jstree({'core':{
    'data':{  
  text        : "Data 1", // node text
  icon        : "string", // string for custom
  state       : {
    opened    : true,  // is the node open
    disabled  : true,  // is the node disabled
    selected  : true,  // is the node selected
  },
  children    : [{text:'number: '+data.layer[0].number},{text:'name: '+data.layer[0].name},{text:'type: '+data.layer[0].type},{text:'thickGauge: '+data.layer[0].thickGauge},{text:'layerPerc: '+data.layer[0].layerPerc},
                {text:'componentTotal: '+data.layer[0].componentTotal},{text:'componenDens: '+data.layer[0].componenDens},{text:'componentCost: '+data.layer[0].componentCost},{text:'component: '+data.layer[0].component[0].name},{text:'percent: '+data.layer[0].component[0].percent}],  // array of strings or objects
  li_attr     : {},  // attributes for the generated LI node
  a_attr      : {},  // attributes for the generated A node
}
    }});        

 $('#jstree_2').jstree({'core':{
    'data':{  
  text        : "Data 2", // node text
  icon        : "string", // string for custom
  state       : {
    opened    : true,  // is the node open
    disabled  : true,  // is the node disabled
    selected  : true,  // is the node selected
  },
  children    : [{text:'number: '+data.layer[1].number},{text:'name: '+data.layer[1].name},{text:'type: '+data.layer[1].type},{text:'thickGauge: '+data.layer[1].thickGauge},{text:'layerPerc: '+data.layer[1].layerPerc},
                {text:'componentTotal: '+data.layer[1].componentTotal},{text:'componenDens: '+data.layer[1].componenDens},{text:'componentCost: '+data.layer[1].componentCost},{text:'component: '+data.layer[1].component[0].name},{text:'component: '+data.layer[1].component[0].percent}],  // array of strings or objects
  li_attr     : {},  // attributes for the generated LI node
  a_attr      : {},  // attributes for the generated A node
}
    }});
    
 $('#jstree_3').jstree({'core':{
    'data':{  
  text        : "Data 3", // node text
  icon        : "string", // string for custom
  state       : {
    opened    : true,  // is the node open
    disabled  : true,  // is the node disabled
    selected  : true,  // is the node selected
  },
  children    : [{text:'number: '+data.layer[2].number},{text:'name: '+data.layer[2].name},{text:'type: '+data.layer[2].type},{text:'thickGauge: '+data.layer[2].thickGauge},{text:'layerPerc: '+data.layer[2].layerPerc},
                {text:'componentTotal: '+data.layer[1].componentTotal},{text:'componenDens: '+data.layer[2].componenDens},{text:'componentCost: '+data.layer[2].componentCost},{text:'component: '+data.layer[2].component[0].name},{text:'component: '+data.layer[2].component[0].percent}],  // array of strings or objects
  li_attr     : {},  // attributes for the generated LI node
  a_attr      : {},  // attributes for the generated A node
}
    }});   




        </script>
    
    

    </body>
    
    </html>

Open in new window

And because I have a creative day I made a function to do all this job as i wrote in the previous post ;)

(function createTree(){
    var i=0;
    var dataLength=data.layer.length
 for(i;i<=dataLength;i++){
    $('#jstree_'+[i+1]).jstree({'core':{
        'data':{  
  text        : "Data "+[i+1], // node text
  icon        : "string", // string for custom
  state       : {
    opened    : false,  // is the node open
    disabled  : true,  // is the node disabled
    selected  : true,  // is the node selected
  },
  children    : [{text:'number: '+data.layer[i].number},{text:'name: '+data.layer[i].name},{text:'type: '+data.layer[i].type},{text:'thickGauge: '+data.layer[i].thickGauge},{text:'layerPerc: '+data.layer[i].layerPerc},
                {text:'componentTotal: '+data.layer[i].componentTotal},{text:'componenDens: '+data.layer[i].componenDens},{text:'componentCost: '+data.layer[i].componentCost},{text:'component: '+data.layer[i].component[0].name},{text:'percent: '+data.layer[i].component[0].percent}],  // array of strings or objects
  li_attr     : {},  // attributes for the generated LI node
  a_attr      : {},  // attributes for the generated A node
}
    }});   
 }
})();

Open in new window

What is the icon attribute?. I see you have strong as value.  So I need it? How do I use it? Example?
Its the
icon        : "string", // string for custom
.You an set your icon via directory path eg
icon        : "/directory_path/icon.png", // string for custom

Open in new window

It looked like it had one by default.  It looked like a folder.

Can I leave it out and have it use the folder?
Of course.Leave it as it is.
Hi.  When you say leave it as is, do you mean just don't have the ICON parameter in the call is okay?

Also,  I'm kind of an intermediate JavaScript person.  Can you explain the way you have the code for the function wrapped like this:
(function createTree(){
    var i=0;
    var dataLength=data.layer.length
 for(i;i<=dataLength;i++){
    $('#jstree_'+[i+1]).jstree({'core':{
        'data':{  
  text        : "Data "+[i+1], // node text
  icon        : "string", // string for custom
  state       : {
    opened    : false,  // is the node open
    disabled  : true,  // is the node disabled
    selected  : true,  // is the node selected
  },
  children    : [{text:'number: '+data.layer[i].number},{text:'name: '+data.layer[i].name},{text:'type: '+data.layer[i].type},{text:'thickGauge: '+data.layer[i].thickGauge},{text:'layerPerc: '+data.layer[i].layerPerc},
                {text:'componentTotal: '+data.layer[i].componentTotal},{text:'componenDens: '+data.layer[i].componenDens},{text:'componentCost: '+data.layer[i].componentCost},{text:'component: '+data.layer[i].component[0].name},{text:'percent: '+data.layer[i].component[0].percent}],  // array of strings or objects
  li_attr     : {},  // attributes for the generated LI node
  a_attr      : {},  // attributes for the generated A node
}
    }});   
 }
})();

Open in new window


When does it get invoked?  Why this instead of
function CreateTree(data) {
//same code
};

Open in new window


Also,  your implementation doesn't account for a random number of components within a layer.  You have a component[0] only whereas in the "real world" there could be several per layer.
So finally I made a small plugin to set the values of the JSON dynamically in jsTree. Comments inside the code:

var data={
	"layer": [            
		{
                                      
			"number": "1",
			"name": "Layer Name One",
			"type": "Core",
			"thickGauge": "323",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "K",
					"percent": "100"
				}
			]                    
		},
		{
   
			"number": "2",
			"name": "Layer Name Two",
			"type": "Core",
			"thickGauge": "3213",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "L",
					"percent": "200"
				}
			]
		},
		{
     
			"number": "3",
			"name": "Layer Name Three",
			"type": "Core",
			"thickGauge": "12333",
			"layerPerc": "100.00",
			"componentTotal": "100.00",
			"componenDens": "Infinity",
			"componentCost": "",
			"component": [
				{
					"name": "M",
					"percent": "300"
				}
			]
		}
	]
};
//I create a function that at the end it rerurns an array of objects to insert it into children
//option inside the jsTree function
function childrenInpupt(arg){
//First I create two var an array (keyD) that it returns this function and an object
//The object is going to have this format because children option (in jsTree function)
//has this format  {text:some string here to show the data of JSON}
  var keyD=[];
  var obj={};
//Then I create a dynamic var that takes the lenght of each elements inside the JSON
//See that I put the [arg] (argument).This is going to set values from the IFEE
//function below.It measures the lenght of the JSON.In this example we have 3 elements (containers)
//of data. 
  var dataElmLenght=Object.keys(data.layer[arg]).length;
//Via for loop I get the keys of each object inside the data.layer
  for(var x=0;x<dataElmLenght;x++){
      var key=Object.keys(data.layer[arg])[x];
//I check of the each key has value string or object.If it is an object the via if
//condition I get the keys of this second object like component
      if((typeof data.layer[arg][key])==='object'){
          var newObj=data.layer[arg][key];
          var lenghtObj=newObj.length;                
//Via for loop again I push the obj inside the keyD array          
          for(var y=0;y<=lenghtObj;y++){           
          var newKey=Object.keys(newObj[0]);         
              obj={text:key+" : "+newKey[y]+" = "+newObj[0][newKey[y]]};          
              keyD.push(obj);           
          }         
          }else{
              obj={text:key+" : "+data.layer[arg][key]};    
              keyD.push(obj);
  }
  }
//And finally i return the keyD array to use it in the jsTree function
             return keyD;  
}

 
(function (){
    var i=0;
    var dataLength=data.layer.length;
     
    for(i;i<dataLength;i++){
 
   
 
    $('#jstree_'+[i+1]).jstree({'core':{
        'data':{  
  text        : "Data "+[i+1], // node text  
  state       : {
    opened    : false,  // is the node open
    disabled  : true,  // is the node disabled
    selected  : true  // is the node selected
  },
  children    : childrenInpupt(i),  // array of strings or objects
  li_attr     : {},  // attributes for the generated LI node
  a_attr      : {}  // attributes for the generated A node
}
    }});
     
 }
})();

Open in new window

Hi.  This is looking promising.  Thank you for all your effort.  Can you explain how to use this function on line 93?  The list I am getting the JSON string from has many rows, where each JSON string is the set of layers and components for that row.  I am not sure how to use this function.

Something like this...

function LoadList() {
  var listItemsObj = getTheList();
  var htmlOut = "<table>";  

  for (var lPtr=0; lPtr<listItemsObj.results.length; lPtr++) {
    var currentItemObj = listItemsObj.results[lPtr];
    //var jsonItem = currentItemObj.layer;  //has the item's layer(s) and component(s) per layer
    htmlOut +="<tr>";
    htmlOut += "<td>currentItemObj.ProjectName</td>";
    htmlOut += "<td>currentItemObj.ProjectType</td>";
    htmlOut += "<td>currentItemObj.ProjectDate</td>";
    htmlOut += "<td>" + "<div>jstree_" + String(lPtr) + "</div></td>";
    htmlOut +="/<tr>";
  }

  htmlOut +="/<table>";

  $('.ListView').html(htmlOut);

}
At line 93 It starts an IFEE anonymous function. In a plain words its a function that at the end invokes itself -->})(); Look at this link IFEE. The function as the document loads invoke itself and then it produce the screen with the folder images as you can see in your page.
You don't have to use IFEE.  You can do a literal function and then you must invoke it as you know eg.
function someFunction(){
//code here
}
someFunction();

Open in new window

The reason I think I need to modify this function is that I need it to iterate through a list of JSON strings and change the associate DIV to the jTree rendered data.

Something like this I think.
	function (rowNumber, LayerForRowJSONObj){
           var data;  //do I need this?
		var i=0;
		
		data = LayerForRowJSONObj;
		var dataLength=data.layer.length;
		 
		for(i;i<dataLength;i++){	 	   	 
			$('#jstree_'+ rowNumber).jstree({'core':{
				'data':{  
				   text        : "Data "+[i+1], // node text  
				   state       : {
					 opened    : false,  // is the node open
					 disabled  : true,  // is the node disabled
					 selected  : true  // is the node selected
				   },
				   children    : childrenInpupt(i),  // array of strings or objects
				   li_attr     : {},  // attributes for the generated LI node
				   a_attr      : {}  // attributes for the generated A node
				}
			}});		 		
    	}	 
	}

Open in new window

Also, please note that there are two pieces to each layer:
Layer Container (three layers in your example)
Each layer has a Component Container (one component in example, but could be 1,2,3 or as many as 10).
The rowNumber arguments it is going to set the jsTree always in a specific DIV. Eg
If the rowNumber=3 then the function it works only for the div with an id="jstree_3".
Basically as I understand well you must follow these steps:
First get the number of layers in your JSON data eg 15
Then you must create dynamically 15 div elements with an id's from 1 to 15.
Then to assign the jsTree function to each of these div for the correspond layer via this function that gave you before (it can be expand and correct) eg
div with an id="1" attach the JSON data.layer[0]
div with an id="2" attach the JSON data.layer[1]
div with an id="3" attach the JSON data.layer[2]
I will try to correct the function as you imagine to be (with more expandable folder for component layer)
The top level is that I have a list that has a column that has a JSON object written as a string.
So, let's say that there are 50 rows.
Each row will have some columns of top level details per row of non JSON info;
    plus a JSON column which represents a layer array with 1 to 10 layers,
    Each layer will have a component array,
    Each component array will have 1 to 8 components.
ASKER CERTIFIED SOLUTION
Avatar of Leonidas Dosas
Leonidas Dosas
Flag of Greece image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial