cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Community Tip - Learn all about PTC Community Badges. Engage with PTC and see how many you can earn! X

How to create 2D/3D components using Javascript from a array of objects (JSON-like structure)

lbviana
6-Contributor

How to create 2D/3D components using Javascript from a array of objects (JSON-like structure)

I am creating a quality inspection app. The user reply to several questions along the experience and the code save this answers in array of objects.

I want to create, in the end of the experience, a "report" that shows the answers of the user from this array of objects. I wan't to do it in a 2D View with several labels, i don't know if there is a better way.

I don't want to use any web services to save and get after, because the user will not have a stable internet in the inspection location. He will save the answer other way when he comes back to his workstation.

I don't want to "hardcode" the labels, because there are 28 answers, would take a lot a time, and any change would be a nightmare.

There is any way?

1 ACCEPTED SOLUTION

Accepted Solutions

Hi @lbviana ,

I think it is not possible to create a dynamically widgets- on the fly - at runtime to add or to remove widgets. Better to say it is not possible in a supported way. So, the most supported approach which you can use is to you to create everything - different layouts version and to blank and to display them depending on your inputs.

In some cases it is possible to use a functionality of repeat region to create reports (only 2D elements /widgets ). So we can use repeater to show a Thing Worx Info Tables. So the repeater are mostly intended to be linked to services /added in the external data section / which result are Info Tables. The infoTables itself handled as JSON object in the AngularJS environment in Studio.

Therefore we can try to provide directly a data as global variable in Studio or we can read it via the http service from a project directory (e.g. upload). So the follow example will read a json file  "ItemList.json"  from the upload  folder and will set to the variable $scope.itemList:

 

 

 

//Global Variables			
var pjson ="ItemList.json"; //JSON File Name without extension
$scope.itemList={}; //global $scope variable

gotJSON=function(data){
 {
$scope.itemList=JSON.parse(data);} 
catch(ex){    console.warn(ex);}
   }
doRead=function (jsonFile){
fetch(jsonFile)
  .then(response=>response.text())
  .then(data=>gotJSON(data))
}

$scope.Init=function() {
doRead('app/resources/Uploaded/'+pjson);
}
/////
$scope.$on('$ionicView.afterEnter',function(){
$scope.Init();}) //event: when 2d View loaded
//the code will read the complette JSON File and
//assignee it to a jsonData global variable

 

 

 

Of course we can also use a static list to set it to a variable e.g.

 

 

 

var ItemList = [
  {"display":"France","id_num":0,"checked":true,"value":"Paris"      ,"src":"test.svg","someText":"some Text123"},        
  {"display":"Italy","id_num":1,"checked":false,"value":"Rome"       ,"src":"test1.svg","someText":""},
  {"display":"Spain","id_num":2,"checked":false,"value":"Madrid"     ,"src":"test2.svg","someText":""},
  {"display":"UK","id_num":3,"checked":false,"value":"London"        ,"src":"test3.svg","someText":""},
  {"display":"Germany","id_num":4,"checked":false,"value":"Berlin"   ,"src":"test4.svg","someText":"-->"},
  {"display":"Norway","id_num":5,"checked":false,"value":"Oslo"      ,"src":"test.svg","someText":""},
  {"display":"Switzerland","id_num":6,"checked":false,"value":"Bern" ,"src":"test1.svg","someText":""},
  {"display":"Greece","id_num":7,"checked":false,"value":"Athens"    ,"src":"test2.svg","someText":""},
  {"display":"France","id_num":8,"checked":true,"value":"Paris"      ,"src":"test3.svg","someText":"other Text"}
             ];//some list got from anywhere

 

 

 

:

 

So now when we have the data in Studio as value of a scope variable or global variable we can try to set it to an repeat region and create something similar as simple report:

2020-04-29_17-43-23.jpg

 

The clue here is to set the list to an app parameter and to link then the application parameter to the repeater data and also to the different repeater elements /widgets in the row/columns of the repeater. This is not trivial because it is not intent usage of the repeater but for such simple case it will work

 

2020-04-29_18-18-04.jpg

 

To be able to link the elements to the correct repeater row we need a filter. Here an example of filter function. I need a window area because this is the only way to pass variable between 2 different calls of the filter. Here an example for filter definition for a label:

 

 

 

//filter for the checkbox element  
 //console.warn("filterLabel1");
if(!window.my_filter3) window.my_filter3=1;
 else {
 if(window.my_filter3>= value.length) window.my_filter3=1;
  else {window.my_filter3++;} }

 return(value[window.my_filter3-1]['someText']);

 

 

 

  e.g. for a image widget:

 

 

 

//filter for the Image Widget element  //
   //console.warn("filterImage1");
if(!window.my_filter3) window.my_filter3=1;
 else {
 if(window.my_filter3>= value.length) window.my_filter3=1;
  else {window.my_filter3++;} }

 return('app/resources/Uploaded/'+value[window.my_filter3-1]['src']);

 

 

 

With little more work we can also implment a function which could change the value on click - e.g. a checkbox to set it to true or false and to update the list:

 

 

 

//==================================================================
$scope.changeValueInJson= function (obj,id_num,field2change, new_value)
{
  
if(new_value==undefined) return;
try{if( obj[0][field2change]==undefined) return;}catch(wrong){console.error("error::"+wrong);return;}

for (var i = 0; i < obj.length; i++){ 
  if (obj[i]['id_num'] == id_num){
               obj[i][field2change]= new_value;}}  
 
 $scope.app.params["ItemList"] = "";
 $scope.app.params["ItemList"] = JSON.parse(JSON.stringify(obj));//check value
 $scope.$applyAsync();   
}
//==================================================================

twx.app.fn.clickItemInRepeater = function(item,list,isMultiSelect)
{
console.warn("called clickItemInRepeater()");
  
  
  $scope.changeValueInJson(list,item.id_num,'checked', item.checked?false:true)
  console.log("clickItemInRepeater::ROW Selected:: "+JSON.stringify(item))
  $scope.setWidgetProp('textArea-2','text',JSON.stringify(item))
  $scope.$applyAsync();  
}; 

//==================================================================

 

 

 

The callback twx.app.fn.clickItemInRepeater = function(item,list,isMultiSelect) {...  will fire when an element is clicked.

At the end we will have a list with labels where  their  content and values and number of rows is defined from a Json object- here the number of row is flexible but the number of column is fixed. Of course, we can define more columns in a row and then with filter (similar to the example) to drive their visibility.

I attached a sample project to this post.

View solution in original post

6 REPLIES 6
WilsonT
13-Aquamarine
(To:lbviana)

Hi @lbviana,

 

Are you using Thingworx platform? If yes, I would suggest you to store the user responses into Thingworx and you can create a report/dashboard of all responses in Thingworx for viewing. You can also bring the response records back into the experience , according to your requirement.

 

 

lbviana
6-Contributor
(To:WilsonT)

Hi @WilsonT 

Thanks for your answer! But i don't have the Thingworx platform, just the Vuforia Studio. As I said the user won't have a stable internet and I don't want to use web services because of that. Anyway, show it in the end, is a requirement for this app.

The answer will be saved after when the user comes back to his workstation, but this is another question.

WilsonT
13-Aquamarine
(To:lbviana)

Hi @lbviana,

 

In order to save the users' responses, we need to have a database/platform (with database) that can communicate with the experience. It all depends on your system setup and requirement, on how (data format) and where (database) the user response to be stored. Vuforia Studio is just an AR authoring platform and doesn't store data.

 

 

lbviana
6-Contributor
(To:WilsonT)

Yes @WilsonT , I Know that is not a database, but I don't want to store the data. I just want to show it to the user all the answers before saving. The app just "saves" temporally the answer in an array, and in the end it will save the answer, after the user confirms all the answers. I know that it is possible in other framework, to iterate over an array and create components from that. The Ionic itself, angular and react.

 

There is anyway to do it? Iterate over an array and create components.

 

Thanks for helping!

Hi @lbviana ,

I think it is not possible to create a dynamically widgets- on the fly - at runtime to add or to remove widgets. Better to say it is not possible in a supported way. So, the most supported approach which you can use is to you to create everything - different layouts version and to blank and to display them depending on your inputs.

In some cases it is possible to use a functionality of repeat region to create reports (only 2D elements /widgets ). So we can use repeater to show a Thing Worx Info Tables. So the repeater are mostly intended to be linked to services /added in the external data section / which result are Info Tables. The infoTables itself handled as JSON object in the AngularJS environment in Studio.

Therefore we can try to provide directly a data as global variable in Studio or we can read it via the http service from a project directory (e.g. upload). So the follow example will read a json file  "ItemList.json"  from the upload  folder and will set to the variable $scope.itemList:

 

 

 

//Global Variables			
var pjson ="ItemList.json"; //JSON File Name without extension
$scope.itemList={}; //global $scope variable

gotJSON=function(data){
 {
$scope.itemList=JSON.parse(data);} 
catch(ex){    console.warn(ex);}
   }
doRead=function (jsonFile){
fetch(jsonFile)
  .then(response=>response.text())
  .then(data=>gotJSON(data))
}

$scope.Init=function() {
doRead('app/resources/Uploaded/'+pjson);
}
/////
$scope.$on('$ionicView.afterEnter',function(){
$scope.Init();}) //event: when 2d View loaded
//the code will read the complette JSON File and
//assignee it to a jsonData global variable

 

 

 

Of course we can also use a static list to set it to a variable e.g.

 

 

 

var ItemList = [
  {"display":"France","id_num":0,"checked":true,"value":"Paris"      ,"src":"test.svg","someText":"some Text123"},        
  {"display":"Italy","id_num":1,"checked":false,"value":"Rome"       ,"src":"test1.svg","someText":""},
  {"display":"Spain","id_num":2,"checked":false,"value":"Madrid"     ,"src":"test2.svg","someText":""},
  {"display":"UK","id_num":3,"checked":false,"value":"London"        ,"src":"test3.svg","someText":""},
  {"display":"Germany","id_num":4,"checked":false,"value":"Berlin"   ,"src":"test4.svg","someText":"-->"},
  {"display":"Norway","id_num":5,"checked":false,"value":"Oslo"      ,"src":"test.svg","someText":""},
  {"display":"Switzerland","id_num":6,"checked":false,"value":"Bern" ,"src":"test1.svg","someText":""},
  {"display":"Greece","id_num":7,"checked":false,"value":"Athens"    ,"src":"test2.svg","someText":""},
  {"display":"France","id_num":8,"checked":true,"value":"Paris"      ,"src":"test3.svg","someText":"other Text"}
             ];//some list got from anywhere

 

 

 

:

 

So now when we have the data in Studio as value of a scope variable or global variable we can try to set it to an repeat region and create something similar as simple report:

2020-04-29_17-43-23.jpg

 

The clue here is to set the list to an app parameter and to link then the application parameter to the repeater data and also to the different repeater elements /widgets in the row/columns of the repeater. This is not trivial because it is not intent usage of the repeater but for such simple case it will work

 

2020-04-29_18-18-04.jpg

 

To be able to link the elements to the correct repeater row we need a filter. Here an example of filter function. I need a window area because this is the only way to pass variable between 2 different calls of the filter. Here an example for filter definition for a label:

 

 

 

//filter for the checkbox element  
 //console.warn("filterLabel1");
if(!window.my_filter3) window.my_filter3=1;
 else {
 if(window.my_filter3>= value.length) window.my_filter3=1;
  else {window.my_filter3++;} }

 return(value[window.my_filter3-1]['someText']);

 

 

 

  e.g. for a image widget:

 

 

 

//filter for the Image Widget element  //
   //console.warn("filterImage1");
if(!window.my_filter3) window.my_filter3=1;
 else {
 if(window.my_filter3>= value.length) window.my_filter3=1;
  else {window.my_filter3++;} }

 return('app/resources/Uploaded/'+value[window.my_filter3-1]['src']);

 

 

 

With little more work we can also implment a function which could change the value on click - e.g. a checkbox to set it to true or false and to update the list:

 

 

 

//==================================================================
$scope.changeValueInJson= function (obj,id_num,field2change, new_value)
{
  
if(new_value==undefined) return;
try{if( obj[0][field2change]==undefined) return;}catch(wrong){console.error("error::"+wrong);return;}

for (var i = 0; i < obj.length; i++){ 
  if (obj[i]['id_num'] == id_num){
               obj[i][field2change]= new_value;}}  
 
 $scope.app.params["ItemList"] = "";
 $scope.app.params["ItemList"] = JSON.parse(JSON.stringify(obj));//check value
 $scope.$applyAsync();   
}
//==================================================================

twx.app.fn.clickItemInRepeater = function(item,list,isMultiSelect)
{
console.warn("called clickItemInRepeater()");
  
  
  $scope.changeValueInJson(list,item.id_num,'checked', item.checked?false:true)
  console.log("clickItemInRepeater::ROW Selected:: "+JSON.stringify(item))
  $scope.setWidgetProp('textArea-2','text',JSON.stringify(item))
  $scope.$applyAsync();  
}; 

//==================================================================

 

 

 

The callback twx.app.fn.clickItemInRepeater = function(item,list,isMultiSelect) {...  will fire when an element is clicked.

At the end we will have a list with labels where  their  content and values and number of rows is defined from a Json object- here the number of row is flexible but the number of column is fixed. Of course, we can define more columns in a row and then with filter (similar to the example) to drive their visibility.

I attached a sample project to this post.

Thanks a lot @RolandRaytchev ! This is exactly what i needed! It will show the answers for the user.

Already implemented in my project!

Loved the other examples, not just labels.Very helpful!

Top Tags