Forum Discussion

peterlu's avatar
peterlu
Champion
7 years ago

recommendations widget API problem

Hi Lithium,

 

Lithium has built-in component <@component id="recommendations.widget.recommended-content-taplet" />

https://community.lithium.com/t5/Recommendations/About-Recommendations/ta-p/62660

 

The problem is that client does not like the look and feel of this component, which means we need to supply our own HTML and CSS to style it up and make it look fancy. This means we need to know what API (v1 / v2) this component is using?

 

Any ideas? I have been asked for this by multiple customers.

 

Peter

  • VikasB I might be wrong, but


    For anonymous users who come directly from a search engine, the words of the search query that brought them to the topic or article are also used in the search query.

    that is not the case, search engines, especially Google do NOT provide the search query a user entered into the search field to the website they are led to (AFAIK)...but doesn't really matter =)...

     

    the issue with recreating specialised standard Lithium components like this is a well known problem, the only half reasonable way to do so is very ugly but works in general without trying to reinvent the wheel and all (unknown) algorithms  Lithium uses internally to put the content of these components together, if you're ready for ugly, read on =D:

     

    The idea is to parse the markup of the default component for whatever we're looking for, usually some form of ID that we can then use to query the API which will return a more flexible object we can build our markup around based on the same data that the standard component uses...this is of course painful for every developer, but Lithium does not leave us other options sometimes...so here you go (read the comments in the code):

     

    <#-- this assumes that you overwrite your default component in studio by creating
    	 a component with the same name and add @override at the end, e.g.
    recommendations.widget.recommended-content-taplet@override then you have <@delegate /> available below --> <#assign markup> <#-- pull in the standard component, will output the component's markup into our variable "markup" --> <@delegate /> </#assign> <#assign ids = [] /> <#-- initialize empty sequence for later use --> <#-- parse the markup for whatever we're looking for, in this case topic ids (probably?) I currently do not know a community where this widget is displayed so if you know one let me know, otherwise you have to figure out the correct regex yourself, I assume the topic links usually end with something like ...-p/<topicid>#<messageid>--> <#local idmatches = markup?matches("-p\\/(\\d+)") /> <#list idmatches as id> <#-- only add an id once --> <#-- the ?groups build-in contains a sequence with [0] = the whole match and [0+groupn] items with their match, in this case the <topicid> --> <#if !ids?seq_contains(id?groups[1])> <#local ids = ids + [id?groups[1]]/> </#if> </#list> <#-- then get thread/topic objects from all extracted ids with the REST API --> <#assign query = "SELECT * FROM messages WHERE id IN(${ids?join(',')})"?url /> <#assign response = rest("2.0", "/search?q=" + query) /> <#-- and now you can basically do whatever you want with your topic objects, you're completely free to build the fanciest widgets your clients want from the same data source that the standard component uses...have fun =D --> <#if response.status == "success"> <div class="your-custom-widget-wrapper"> <#list response.data.items as topic> <div class="your-custom-widget-item"> <h3>${topic.subject}</h3> </div> </#list> </div> </#if>  

     

  • peterlu

    Recommendations component creates a list of recommended posts related to the selected post. The list is scoped to the current community, category, or board the user is viewing.

    The component searches on the words in the topic or article subject a user is currently viewing and returns related content that best match this query.

    For anonymous users who come directly from a search engine, the words of the search query that brought them to the topic or article are also used in the search query.
    So as per my assumption either you need to use the search API or word match API as this component also searches on the words in the topic or article subject.

    • peterlu's avatar
      peterlu
      Champion
      VikasB the problem is that we do not know which search api Lithium is using (one_or_more, or exact words etc) and what algorithm. Customer wants the result message list IDs to be the exact same as the built-in component, but with a different Look and Feel. Currently I have to use Javascript solutions to do it, but I would prefer we have the API support, so we can build very fancy components which customers love.
  • VikasB I might be wrong, but


    For anonymous users who come directly from a search engine, the words of the search query that brought them to the topic or article are also used in the search query.

    that is not the case, search engines, especially Google do NOT provide the search query a user entered into the search field to the website they are led to (AFAIK)...but doesn't really matter =)...

     

    the issue with recreating specialised standard Lithium components like this is a well known problem, the only half reasonable way to do so is very ugly but works in general without trying to reinvent the wheel and all (unknown) algorithms  Lithium uses internally to put the content of these components together, if you're ready for ugly, read on =D:

     

    The idea is to parse the markup of the default component for whatever we're looking for, usually some form of ID that we can then use to query the API which will return a more flexible object we can build our markup around based on the same data that the standard component uses...this is of course painful for every developer, but Lithium does not leave us other options sometimes...so here you go (read the comments in the code):

     

    <#-- this assumes that you overwrite your default component in studio by creating
    	 a component with the same name and add @override at the end, e.g.
    recommendations.widget.recommended-content-taplet@override then you have <@delegate /> available below --> <#assign markup> <#-- pull in the standard component, will output the component's markup into our variable "markup" --> <@delegate /> </#assign> <#assign ids = [] /> <#-- initialize empty sequence for later use --> <#-- parse the markup for whatever we're looking for, in this case topic ids (probably?) I currently do not know a community where this widget is displayed so if you know one let me know, otherwise you have to figure out the correct regex yourself, I assume the topic links usually end with something like ...-p/<topicid>#<messageid>--> <#local idmatches = markup?matches("-p\\/(\\d+)") /> <#list idmatches as id> <#-- only add an id once --> <#-- the ?groups build-in contains a sequence with [0] = the whole match and [0+groupn] items with their match, in this case the <topicid> --> <#if !ids?seq_contains(id?groups[1])> <#local ids = ids + [id?groups[1]]/> </#if> </#list> <#-- then get thread/topic objects from all extracted ids with the REST API --> <#assign query = "SELECT * FROM messages WHERE id IN(${ids?join(',')})"?url /> <#assign response = rest("2.0", "/search?q=" + query) /> <#-- and now you can basically do whatever you want with your topic objects, you're completely free to build the fanciest widgets your clients want from the same data source that the standard component uses...have fun =D --> <#if response.status == "success"> <div class="your-custom-widget-wrapper"> <#list response.data.items as topic> <div class="your-custom-widget-item"> <h3>${topic.subject}</h3> </div> </#list> </div> </#if>  

     

    • peterlu's avatar
      peterlu
      Champion
      luk what a hack. I like it. Server side hack :) I did it by JS. Yours looks better.
      • luk's avatar
        luk
        Boss

        Yeah, pretty horrendous I know, but there was no other way, I originally used it for some leaderboard components where no API calls were available and it worked surprisingly well =D...but yeah, it's not that all your components should now look like this (I already see a new badge on the Lithosphere introduced -> >You earned a new badge "Bad Influence"...). It's just the everyday issue with the standard components, everything would be so much easier if there was a way to access a components data model in FreeMarker, all these hacks would be obsolete if we could just set some kind of xml paramenter like 

         

         

        <@component id="<cmpname>" data="true" />
        <#-- this would then set an env.data variable or something in that direction -->
        <#-- then we could do regular FreeMarker uglyness -->
        
        <#list env.data.items as item>
            ${item.subject}
        </#list>
        
        <#-- and if we're already there, for debugging purposes
             (to know what's actually there, another variable would be useful too:
        -->
        
        <@component id="<cmpname>" data="true" json="true" />
        
        <#-- which would just output a JSON representation of the components data model -->

        DougS might know if something like this is even possible, but I guess there are no plans atm...?

         

         

    • Nath's avatar
      Nath
      Guide

      this solution does not work anymore ?

      <#local idmatches = markup?matches("-p\\/(\\d+)") />

      return me : " Local variable assigned outside a macro."

      • VikasB's avatar
        VikasB
        Boss

        Nath
        Seems like you are running this code inside the component.  A local variable can be used inside the macros or functions only. 
        Replace "local" with "assign" if you want to use it in the component. 

        <#assign idmatches = markup?matches("-p\\/(\\d+)") />