Hierarchical asset list with server side javascript (SSJ)


(Jelina Kilpatrick) #1

Matrix v5.4.5.1

Is it possible to create a hierarchical list of assets (and their children) with server side javascript?

For example, if my asset map looked like this:

  • Item 1
  • Item 1.1
  • Item 1.2
  • Item 1.3
  • Item 2
  • Item 3

I would like to be able to produce a marked up list like this:

<ul>
  <li>Item 1
    <ul>
      <li>Item 1.1</li>
      <li>Item 1.2</li>
      <li>Item 1.3</li>
    </ul>
  </li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

I’ve tried to do this with SSJ with no luck. I’m not sure if it’s because of the keywords execution order or some other issue, but it seems like it should be so simple. This is an example of what I’ve tried:

<ul>
<script runat="server">

const assets = %globals_asset_children_link_type_1:1234^as_asset:asset_assetid,asset_name^empty:[]%;

assets.forEach(function(asset) {
  
  const assetChildren = %globals_asset_children_link_type_1:asset.asset_assetid^as_asset:asset_assetid,asset_name^empty:[]%;
  
  print('<li>' + asset.asset_name);
  
  if (assetChildren) {
    print('<ul>');
    
    assetChildren.forEach(function(assetChild) {
      print('<li>' + assetChild.asset_name + '</li>');
    }
    
    print('</ul>');
  }
  
  print('</li>');
});
</script>
</ul>

(John gill) #2

I don’t think this is possible just with SSJS, due to the limitations of the execution order.

In general, you have to think of it as three steps:

  1. Set up some data with keywords
  2. Run the JS on that data, including possibly printing some new keywords for display (%globals_ only)
  3. The keywords you printed in the SSJS get evaluated

Inevitably you find yourself wanting step 4 - you want to run some JS on the data that you got from the keyword that you built with JS. But you can’t.

For your case, there might be a better way but I would use an Asset Listing for the first level of iteration and then SSJS for printing out the children of each item. It’s not super, but it at least means one less Asset Listing than the old way.

Previewing the JS that Matrix will run by adding ?SQ_VIEW_SERVER_JS on the end of the URL is very useful for keeping a clear picture of what it’s actually doing.

https://matrix.squiz.net/manuals/concepts/chapters/server-side-javascript#viewing-ssjs


(John gill) #3

Of course, if you don’t mind having a hard coded limit to how many it can handle, you could do something really disgusting like

<script runat="server">
const children = %globals_asset_children_link_type_1:103^empty:[]%;

const grandChildren = 
[
 %globals_asset_children_link_type_1:103^index:0^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:1^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:3^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:4^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:5^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:6^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:7^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:8^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:9^as_asset:asset_children_link_type_1^empty:[]%
,%globals_asset_children_link_type_1:103^index:10^as_asset:asset_children_link_type_1^empty:[]%
]

children.forEach(function(child, i) {
  print('<li>%globals_asset_name:' + child + '%');
  if (grandChildren[i].length > 0) {
    print('<ul>');
    grandChildren[i].forEach(function(grandChild) {
      print('<li>%globals_asset_name:' + grandChild + '%</li>');
    });
    print('</ul>');
  }
  print('</li>');
});
</script>

which becomes something like

const children = ["179","107","111","115","119","123","127"];

const grandChildren = 
[
 []
,["131","135"]
,["147","151","155"]
,["159"]
,["163","167","171"]
,["175"]
,[]
,[]
,[]
,[]
]


children.forEach(function(child, i) {
 
  print('<li>%globals_asset_name:' + child + '%');
  
  if (grandChildren[i].length > 0) {
    print('<ul>');
    
    grandChildren[i].forEach(function(grandChild) {
      print('<li>%globals_asset_name:' + grandChild + '%</li>');
    });
    
    print('</ul>');
  }
  
  print('</li>');
});

but you’d have to really want to avoid using an Asset Listing to go with that. On the other hand, if a hardcoded limit is acceptable this might perform better than an Asset Listing. Or worse. ¯\_(ツ)_/¯


JS: Accessing all assets located within sub folders from parent
Google Structured Data Markup
(Jelina Kilpatrick) #4

Thanks @JohnGill — I couldn’t get the asset list to work (as per your first example) but that was probably a mistake I made somewhere. I went with your second example because, while the hardcoded limit is annoying, the result was the closest I could get to what I needed.

It’s unfortunate we can’t iterate through all the assets by default with SSJ :frowning:


(John gill) #5

@jelina While I was answering a different question I realised something about ^as_asset in the post-5.4.2.2 world. You can remove the limitation on the above by switching it to.

<script runat="server">
const children = %globals_asset_children_link_type_1:103^empty:[]%;
const grandChildren = %globals_asset_children_link_type_1:103^as_asset:asset_children_link_type_1^empty:[]%;

children.forEach(function(child, i) {
  print('<li>%globals_asset_name:' + child + '%');
  if (grandChildren[i].length > 0) {
    print('<ul>');
    grandChildren[i].forEach(function(grandChild) {
      print('<li>%globals_asset_name:' + grandChild + '%</li>');
    });
    print('</ul>');
  }
  print('</li>');
});
</script> 

No guarantees it works for the various edge-cases, although it handles the “child has no grandchildren” case fine.

Perfwise, using ^as_asset to extract array properties from an array might be awful but it’s probably still better than an asset listing. Probably.