• REGISTRATION REQUIREMENTS:

    Your username here MUST MATCH your XenForo username (connected to your XF license).

    Once you have registered here, then you need to start a conversation at xenforo.com w/Bob and provide the following:
    1. Your XenForo License Validation Token
    2. The Domain Name associated with the License
    NOTE: Your account will be validated once ALL requirements are verified/met. Thank you for your patience.

Using Custom Fields in Pages

MattW

Server Admin
AMS Premium
CAS Premium
RMS Premium
SC Premium
UBS Premium
In this example, I'll explain how I accessed my custom fields to used them to make a table using Google Charts API

OK, so the first thing you are going to need to do is create your own Model to expose the data required.

In my example, I created a Garage folder inside library. You will then need a ControllerPublic folder

/library/Garage/ControllerPublic

The file I created was called Garage.php, and this contains the functions needed.

The class used is Garage_ControllerPublic_Garage

I then have 3 functions used to get the various information I'm using (3 as I use this file for 3 different pages)

PHP:
<?php
class Garage_ControllerPublic_Garage
{
        public static function getPowerBoard(XenForo_ControllerPublic_Abstract $controller, XenForo_ControllerResponse_Abstract &$response)
        {
                $db = XenForo_Application::getDb();
       
                $power = $db->fetchAll("
                SELECT
                        xf_nflj_showcase_custom_field_value.*,
                        xf_nflj_showcase_item.item_id,
                        xf_nflj_showcase_item.user_id,
                        xf_nflj_showcase_item.item_name,
                        xf_user.user_id,
                        xf_user.username
                FROM xf_nflj_showcase_custom_field_value
                LEFT JOIN xf_nflj_showcase_item ON
                        (xf_nflj_showcase_custom_field_value.item_id = xf_nflj_showcase_item.item_id)
                LEFT JOIN xf_user ON
                        (xf_nflj_showcase_item.user_id = xf_user.user_id)
                WHERE xf_nflj_showcase_custom_field_value.field_id =  'bhp'
                AND xf_nflj_showcase_custom_field_value.field_value !=  ''
                ORDER BY xf_nflj_showcase_custom_field_value.field_value DESC
                ");

                $response->params['power'] = $power;
        }

        public static function getCategory(XenForo_ControllerPublic_Abstract $controller, XenForo_ControllerResponse_Abstract &$response)
        {
                $db = XenForo_Application::getDb();

                $category = $db->fetchAll("
                SELECT
                        category_name,
                        item_count
                FROM xf_nflj_showcase_category
                ");

                $response->params['category'] = $category;
        }

        public static function getQuarterMile(XenForo_ControllerPublic_Abstract $controller, XenForo_ControllerResponse_Abstract &$response)
        {
                $db = XenForo_Application::getDb();

                $power = $db->fetchAll("
                SELECT
                        xf_nflj_showcase_custom_field_value.*,
                        xf_nflj_showcase_item.item_id,
                        xf_nflj_showcase_item.user_id,
                        xf_nflj_showcase_item.item_name,
                        xf_user.user_id,
                        xf_user.username
                FROM xf_nflj_showcase_custom_field_value
                LEFT JOIN xf_nflj_showcase_item ON
                        (xf_nflj_showcase_custom_field_value.item_id = xf_nflj_showcase_item.item_id)
                LEFT JOIN xf_user ON
                        (xf_nflj_showcase_item.user_id = xf_user.user_id)
                WHERE xf_nflj_showcase_custom_field_value.field_id =  '14_Mile'
                AND xf_nflj_showcase_custom_field_value.field_value !=  ''
                ORDER BY xf_nflj_showcase_custom_field_value.field_value DESC
                ");

                $response->params['power'] = $power;
        }
   
}

So taking the function getPowerBoard this is what will be used to pull the power figures from my custom field bhp

basic_info.PNG general_options.PNG options.PNG

The below is the query and response we are going to pass back to the template

PHP:
$power = $db->fetchAll("
                SELECT
                        xf_nflj_showcase_custom_field_value.*,
                        xf_nflj_showcase_item.item_id,
                        xf_nflj_showcase_item.user_id,
                        xf_nflj_showcase_item.item_name,
                        xf_user.user_id,
                        xf_user.username
                FROM xf_nflj_showcase_custom_field_value
                LEFT JOIN xf_nflj_showcase_item ON
                        (xf_nflj_showcase_custom_field_value.item_id = xf_nflj_showcase_item.item_id)
                LEFT JOIN xf_user ON
                        (xf_nflj_showcase_item.user_id = xf_user.user_id)
                WHERE xf_nflj_showcase_custom_field_value.field_id =  'bhp'
                AND xf_nflj_showcase_custom_field_value.field_value !=  ''
                ORDER BY xf_nflj_showcase_custom_field_value.field_value DESC
                ");

                $response->params['power'] = $power;

The two important lines of that are

WHERE xf_nflj_showcase_custom_field_value.field_id = 'bhp'
AND xf_nflj_showcase_custom_field_value.field_value != ''

which is the name of the custom field, and the AND statement makes it only pull results where there is data (ie not empty)

For the XenForo page, you want to create a page using a PHP callback to the Class and Function

call_back.PNG

This will then allow you access to the response params

The HTML used to make the page, which works with the Google Charts API

HTML:
<style type="text/css">
.outercontainer {
border: 1px solid #BAC2C7;
padding: 10px;
margin: 0 auto;
background-color: #F4F6F7 !important;
}
</style>
<script type='text/javascript' src='https://www.google.com/jsapi'></script>
    <script type='text/javascript'>
      google.load('visualization', '1', {packages:['table']});
      google.setOnLoadCallback(drawTable);
      function drawTable() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'Member');
        data.addColumn('string', 'Vehicle');
        data.addColumn('string', 'Power');
        data.addRows([
          <xen:foreach loop="$power" value="$power">
          ['<xen:username user="$power" />', '<a href="{xen:link showcase, $power}">{xen:jsescape {xen:raw $power.item_name}, single}</a>', '{$power.field_value} {$power.field_id}'],
          </xen:foreach>
        ]);
        var table = new google.visualization.Table(document.getElementById('table_div'));
        table.draw(data, {showRowNumber: true, allowHtml: true});
      }
    </script>

<div class="outercontainer">
<div class="baseHtml">
<div id='table_div'></div>
</div>
</div>

You want to loop through the results, with the below code:

<xen:foreach loop="$power" value="$power">
['<xen:username user="$power" />', '<a href="{xen:link showcase, $power}">{xen:jsescape {xen:raw $power.item_name}, single}</a>', '{$power.field_value} {$power.field_id}'],
</xen:foreach>

This links the user back to their profile, their vehicle to their showcase item, and pull the power value from the custom fields. You can see the $power.xxxx_xxxx which relates to the response and the specific field, ie $power.item_name

Hope this helps, any questions, please ask.
 
Alrighty. I'll finally get my dev box set up so I can get to learning with this project. I've been taking a break of sorts after the import. Time to get back to it. If I keep spinning my wheels I'll send you a note for some custom work. Thanks Bob.

I'm sure I'll find this out on my own eventually, but I'll ask anyway - if I get Matt's example to work the way I need it to can I make that page display as a tab within Showcase, as another listing option on the index_list and category_list pages?
 
I'm sure I'll find this out on my own eventually, but I'll ask anyway - if I get Matt's example to work the way I need it to can I make that page display as a tab within Showcase, as another listing option on the index_list and category_list pages?

Not without some rather wicked modification to the existing controllers and templates. Would take me all day long typing to explain why for each one (Index, Category and Item). You'd need to create and addon and extend several methods in controllers and models as well as some rather complex template modifications (using the TM system).

Might be better off starting an inbox with me explaining the ENTIRE project top to bottom, left to right so I can recommend the best course of action.
 
The way Matt is doing it is the best way to do it IMO. The only thing wrong with his example is that he is not taking item_state into consideration as it will pull draft, visible, moderated and deleted items. You can fix that by adding in the and statement that I posted above.

I DO, do custom work yano ;) I just know that you like doing things on your own and learning. THIS IMO is the best way to learn. Take what Matt did, look at the Showcase Code (reverse engineer it) and go from there.
Just updated my query to include the item_state :)
 
  • Like
Reactions: Bob
OK, so I'm looking at modifying the query to pull multiple custom field values to display them in the page. My first thought was just just add a bunch of AND lines to include all the fields I want to have included, like so:
Code:
        public static function getTimeSlips(XenForo_ControllerPublic_Abstract $controller, XenForo_ControllerResponse_Abstract &$response)
        {
                $db = XenForo_Application::getDb();

                $times = $db->fetchAll("
                SELECT
                        xf_nflj_showcase_custom_field_value.*,
                        xf_nflj_showcase_item.item_id,
                        xf_nflj_showcase_item.user_id,
                        xf_nflj_showcase_item.item_name,
                        xf_user.user_id,
                        xf_user.username
                FROM xf_nflj_showcase_custom_field_value
                LEFT JOIN xf_nflj_showcase_item ON
                        (xf_nflj_showcase_custom_field_value.item_id = xf_nflj_showcase_item.item_id)
                LEFT JOIN xf_user ON
                        (xf_nflj_showcase_item.user_id = xf_user.user_id)
                WHERE xf_nflj_showcase_custom_field_value.field_id =  'vehicle_timeslip_et'
                AND xf_nflj_showcase_custom_field_value.field_id =  'vehicle_timeslip_mph'
         AND xf_nflj_showcase_custom_field_value.field_id =  'vehicle_timeslip_60foot'
         AND xf_nflj_showcase_custom_field_value.field_id =  'vehicle_timeslip_8th_mph'
         AND xf_nflj_showcase_custom_field_value.field_id =  'vehicle_timeslip_eighth'
         AND xf_nflj_showcase_custom_field_value.field_id =  'vehicle_timeslip_photo'
         AND xf_nflj_showcase_custom_field_value.field_id =  'vehicle_timeslip_video'
         AND xf_nflj_showcase_custom_field_value.field_value !=  ''
         AND xf_nflj_showcase_item.item_state = 'visible'
                ORDER BY xf_nflj_showcase_custom_field_value.field_value DESC
                ");

                $response->params['times'] = $times;
        }
But then there's the issue of displaying those fields in the framework set forth in this tutorial. Could I just modify the code to look like this:
Code:
<style type="text/css">
.outercontainer {
border: 1px solid #BAC2C7;
padding: 10px;
margin: 0 auto;
background-color: #F4F6F7 !important;
}
</style>
<script type='text/javascript' src='https://www.google.com/jsapi'></script>
    <script type='text/javascript'>
      google.load('visualization', '1', {packages:['table']});
      google.setOnLoadCallback(drawTable);
      function drawTable() {
        var data = new google.visualization.DataTable();
         data.addColumn('string', 'Timeslip');
         data.addColumn('string', 'Member');
        data.addColumn('string', 'Vehicle');
        data.addRows([
          <xen:foreach loop="$times" value="$times">
          ['{$times.field_value.vehicle_timeslip_et} @ {$times.field_value.vehicle_timeslip_mph}', '<xen:username user="$times" />', '<a href="{xen:link showcase, $times}">{xen:jsescape {xen:raw $times.item_name}, single}</a>'],
          </xen:foreach>
        ]);
        var table = new google.visualization.Table(document.getElementById('table_div'));
        table.draw(data, {showRowNumber: true, allowHtml: true});
      }
    </script>

<div class="outercontainer">
<div class="baseHtml">
<div id='table_div'></div>
</div>
</div>
 
Last edited:
Just FYI (for those that have advanced programming skills)... I've added the ability to fetch items by custom fields to the NFL_Showcase_Model_Item::getItems method which can also be used for this stuff. Its high level (advanced) stuff tho, so don't expect a tutorial anytime soon (you can get an idea on how to use it by reverse engineering the new Field Search functionality).

@Ludachris what isn't working?
 
So should I scrap the code I posted and start over using something different based on your new code in the field search files? I have two functions in my Garage.php file that I created based on this tutorial. One function has the dyno field data, which looks to be working on the dyno page I created. But the timeslip page isn't displaying data. I'll keep f'n around with it. I just don't know if I'm close or if I'm way off in the wrong direction.
 
Im not suggesting anything. I simply made a statement for anyone reading this that has advanced development skills that I've added some code that will allow the fetching of items by custom fields and you can see it in use within the new Field Search (that is currently the only place that I am using it).

In this case, you should really just inbox @MattW and ask him about it since its his code and it is working on his site. The way Matt is doing it is perfectly acceptable (with the addition of making sure only VISIBLE items are being pulled that is, but he corrected that).

Can you get more complex? Sure, however, it requires an advanced understanding of the xenforo architecture as well as showcase architecture. Being able to understand the architecture (reverse engineer the fetch process to include the entire pre fetch process of preparing conditions and fetch options) is vital. If you are not at that level yet, you will just end up getting all pissed off when stuff doesn't work. Trust me, I've been there and it really wasn't that long ago. I learn new stuff about xenforo on a daily basis.
 
Got Matt to reply, hoping he'll have some time to help me out on my test site.

In terms of coding the CallBack controller (in this case, the Garage.php file), and please don't beat me up too bad for my limited coding experience, couldn't you just make calls to the functions that you have written for showcase to pre-fetch and prepare the items? I'm guessing not, probably because this is outside of showcase and it needs to be customized. It just seems like the coding has already been written, it should just be a matter of tweaking it to work for this. That's my non-programmer analysis at least :) LOL

Anyway, hopefully Matt can help me figure out how to pull in more of the item field values to use on these pages.
 
For example, the $conditions and $fetchoptions need to be prepared BEFORE the fetch process as they are PART of the fetch process.
NFLJ_Showcase_Model_Item::getItems(array $conditions, array $fetchOptions)
Fetch showcase items based on the conditions and options specified

Just look at what I have to do before I can even fetch items for showcase home... all of the below code is preparing the $conditions and $fetchoptions.

PHP:
        $scCategoryModel = $this->_getSCCategoryModel();
        $scItemModel = $this->_getSCItemModel();
       
        $indexSortTabs = XenForo_Application::get('options')->scIndexSortTabs;
        $defaultOrder = XenForo_Application::get('options')->scIndexSortOrder;
               
        if (!$indexSortTabs[$defaultOrder])
        {
            $indexSortTabs['recent'] = 1;
            $defaultOrder = 'recent';
        }      
        $defaultOrderDirection = 'DESC'; // do not change this!
        $defaultTypeFilter = ''; // do not change this!
        $defaultRatingFilter = ''; // do not change this!
       
        $order = $this->_input->filterSingle('order', XenForo_Input::STRING, array('default' => $defaultOrder));
        $orderDirection = $this->_input->filterSingle('direction', XenForo_Input::STRING, array('default' => $defaultOrderDirection));
       
        $typeFilter = $this->_input->filterSingle('type', XenForo_Input::STRING);
        $ratingFilter = $this->_input->filterSingle('rating', XenForo_Input::INT);
        $prefixFilter = $this->_input->filterSingle('prefix_id', XenForo_Input::INT);

        $criteria = array();
       
        if (!in_array($typeFilter, array('featured','rated','reviewed')))
        {
            $typeFilter = false;
        }
        if (!in_array($ratingFilter, array(5,4,3,2)))
        {
            $ratingFilter = false;
        }       

        $criteria['type'] = ($typeFilter ? $typeFilter : $defaultTypeFilter);
        $criteria['rating'] = ($ratingFilter ? $ratingFilter : $defaultRatingFilter);
               
        if ($prefixFilter)
        {
            $criteria['prefix_id'] = $prefixFilter;
        }       
       
        $imageRequired = (($layoutType == 'grid') ? XenForo_Application::get('options')->scGridImageRequired : false);
       
        $order = ($order ? $order : $defaultOrder);

        if ($order == 'atoz')
        {
            $orderDirection = 'ASC';           
        }
       
        $criteria['image'] = ($imageRequired == 1) ? true : false;
        $criteria['rated'] = ($order == 'rated') ? true : false;
        $criteria['reviewed'] = ($order == 'reviewed') ? true : false;
       
        $criteria += $scCategoryModel->getPermissionBasedFetchConditions();
       
        $viewableCategories = $this->_getSCCategoryModel()->getViewableCategories();
        $criteria['category_id'] = array_keys($viewableCategories);
       
        $categoryList = $scCategoryModel->groupCategoriesByParent($viewableCategories);
        $categoryList = $scCategoryModel->applyRecursiveCountsToGrouped($categoryList);
        $categories = isset($categoryList[0]) ? $categoryList[0] : array();
       
        $page = max(1, $this->_input->filterSingle('page', XenForo_Input::UINT));
        $perPage = XenForo_Application::get('options')->scIndexItemsPerPage;       
       
        if (!empty($criteria['type']) || !empty($criteria['rating']) || !empty($criteria['prefix_id']) || $criteria['deleted'] === true || $criteria['moderated'] === true
            ||  $criteria['rated'] === true ||  $criteria['reviewed'] === true ||  $criteria['image'] === true)
        {
            $totalItems = $scItemModel->getItemsCount($criteria, array('join' => NFLJ_Showcase_Model_Item::FETCH_CATEGORY));
        }
        else
        {
            $totalItems = 0;
            foreach ($categories AS $category)
            {
                $totalItems += $category['item_count'];
            }
        }
 
For example, the $conditions and $fetchoptions need to be prepared BEFORE the fetch process as they are PART of the fetch process.
NFLJ_Showcase_Model_Item::getItems(array $conditions, array $fetchOptions)
Fetch showcase items based on the conditions and options specified

Just look at what I have to do before I can even fetch items for showcase home... all of the below code is preparing the $conditions and $fetchoptions.

PHP:
        $scCategoryModel = $this->_getSCCategoryModel();
        $scItemModel = $this->_getSCItemModel();
      
        $indexSortTabs = XenForo_Application::get('options')->scIndexSortTabs;
        $defaultOrder = XenForo_Application::get('options')->scIndexSortOrder;
              
        if (!$indexSortTabs[$defaultOrder])
        {
            $indexSortTabs['recent'] = 1;
            $defaultOrder = 'recent';
        }     
        $defaultOrderDirection = 'DESC'; // do not change this!
        $defaultTypeFilter = ''; // do not change this!
        $defaultRatingFilter = ''; // do not change this!
      
        $order = $this->_input->filterSingle('order', XenForo_Input::STRING, array('default' => $defaultOrder));
        $orderDirection = $this->_input->filterSingle('direction', XenForo_Input::STRING, array('default' => $defaultOrderDirection));
      
        $typeFilter = $this->_input->filterSingle('type', XenForo_Input::STRING);
        $ratingFilter = $this->_input->filterSingle('rating', XenForo_Input::INT);
        $prefixFilter = $this->_input->filterSingle('prefix_id', XenForo_Input::INT);

        $criteria = array();
      
        if (!in_array($typeFilter, array('featured','rated','reviewed')))
        {
            $typeFilter = false;
        }
        if (!in_array($ratingFilter, array(5,4,3,2)))
        {
            $ratingFilter = false;
        }      

        $criteria['type'] = ($typeFilter ? $typeFilter : $defaultTypeFilter);
        $criteria['rating'] = ($ratingFilter ? $ratingFilter : $defaultRatingFilter);
              
        if ($prefixFilter)
        {
            $criteria['prefix_id'] = $prefixFilter;
        }      
      
        $imageRequired = (($layoutType == 'grid') ? XenForo_Application::get('options')->scGridImageRequired : false);
      
        $order = ($order ? $order : $defaultOrder);

        if ($order == 'atoz')
        {
            $orderDirection = 'ASC';          
        }
      
        $criteria['image'] = ($imageRequired == 1) ? true : false;
        $criteria['rated'] = ($order == 'rated') ? true : false;
        $criteria['reviewed'] = ($order == 'reviewed') ? true : false;
      
        $criteria += $scCategoryModel->getPermissionBasedFetchConditions();
      
        $viewableCategories = $this->_getSCCategoryModel()->getViewableCategories();
        $criteria['category_id'] = array_keys($viewableCategories);
      
        $categoryList = $scCategoryModel->groupCategoriesByParent($viewableCategories);
        $categoryList = $scCategoryModel->applyRecursiveCountsToGrouped($categoryList);
        $categories = isset($categoryList[0]) ? $categoryList[0] : array();
      
        $page = max(1, $this->_input->filterSingle('page', XenForo_Input::UINT));
        $perPage = XenForo_Application::get('options')->scIndexItemsPerPage;      
      
        if (!empty($criteria['type']) || !empty($criteria['rating']) || !empty($criteria['prefix_id']) || $criteria['deleted'] === true || $criteria['moderated'] === true
            ||  $criteria['rated'] === true ||  $criteria['reviewed'] === true ||  $criteria['image'] === true)
        {
            $totalItems = $scItemModel->getItemsCount($criteria, array('join' => NFLJ_Showcase_Model_Item::FETCH_CATEGORY));
        }
        else
        {
            $totalItems = 0;
            foreach ($categories AS $category)
            {
                $totalItems += $category['item_count'];
            }
        }
Hey Bob, I know there's no real benefit to you answering questions like this for someone like me who is very inexperienced when it comes to programming. I know it's a time suck. Thanks for doing it. I hate wasting your time knowing you've got so much other stuff going on. I know I need to learn more about the XF and Showcase architecture, as well as just PHP scripting in general to see (among other things) when, where and how I can call existing methods to take advantage of code that does what I'm trying to do so that I'm not trying to duplicate what's already been done.

What I'd really like to do is just duplicate the showcase Index page (in List view) - call items and custom showcase field data and display it almost exactly the way the Index page list displays items, only with a few extra custom fields displayed. Could this be achieved easier by creating a Widget page? I really need to learn how to create simple plugins.
 
To do what Showcase Home is doing requires its own Controller and View, so no, it can not be done with a widget. Wanting to duplicate Showcase Home is a pretty large advanced level customization, but probably something I would tackle if I was learning (which I still do on a weekly basis figuring stuff out).

It doesn't get any easier than what Matt is doing in his examples, it only gets much more complex and harder to understand.
 
To do what Showcase Home is doing requires its own Controller and View, so no, it can not be done with a widget. Wanting to duplicate Showcase Home is a pretty large advanced level customization, but probably something I would tackle if I was learning (which I still do on a weekly basis figuring stuff out).

It doesn't get any easier than what Matt is doing in his examples, it only gets much more complex and harder to understand.
Just so I know if I'm on the right track about the controllers and views (I've messed around a little with some Ruby on Rails installations so I have a little bit of understanding with the MVC structure) I would basically take the '/Showcase/ControllerPublic/showcase.php' file, make a copy and rename, and focus on the List view code and modify that... then I'd essentially do the same with the View, which is where exactly?

Where it's easy to get lost is locating the files in the file system to find what does what.
 
What IDE are you using that you get LOST locating files? The CLASS NAME of each file tells you exactly where the FILE is located..

class NFLJ_Showcase_ViewPublic_Index_View = /library/NFLJ/Showcase/ViewPublic/Index/View.php
Selection_200.png
Here you can see the above file in the file system through the IDE's PHP Explorer.

Selection_199.png

Selection_198.png

Selection_197.png
 
As far as your comment about just copying the entire Showcase Controller? NO NO NO NO NO. Borrow some code from it, yes, but the ENTIRE FILE? 90% of it has nothing to do with the showcase home page.
 
Back
Top