1

I am using AngularJS to load a list of my WordPress posts, however I can not get any of my PHP functions to work with my partials file.

I've tried using something such as search.php instead of search.html but when doing so I get errors such as fatal error get_post_meta is undefined.

Now I know we are not suppose to mix client side with server side, and I can possibly use some kind of service to parse my PHP, but I have no idea how to go about it. I need my search.php to render my PHP tags so I can display custom fields, and use several PHP functions I have there.

Whats the best way of doing this?

On my page template (.php) I have --

<div id="page" ng-app="app">
        <header>
            <h1>
                <a href="<?php echo home_url(); ?>">Search</a>
            </h1>
        </header>
  <?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>

   <div ng-Cloak ng-controller="MyController" class="my-controller">
<div ng-view></div>     
</div>
  <?php endwhile; ?>
 <?php endif; ?>
<?php rewind_posts(); ?>
<div ng-controller="OtherController" class="other-controller">
          <div class="text-center">
          <dir-pagination-controls boundary-links="true" on-page-change="pageChangeHandler(newPageNumber)" template-url="/partials/dirPagination.tpl.html"></dir-pagination-controls>
          </div>
        </div>
        <footer>
            &copy; <?php echo date( 'Y' ); ?>
        </footer>
    </div>

And on my PHP file I want to be called into view has functions such as --

<?php 
$pcomp1b = get_post_meta(get_the_ID(), 'pa_meta_comp1b', true); 
$pcomp1c = get_post_meta(get_the_ID(), 'pa_meta_comp1c', true);
$pcomp1d = get_post_meta(get_the_ID(), 'pa_meta_comp1d', true); ?>

Math --

if( is_numeric( $price1 ) ) {
    $a1 = $price1;
}
$b1 = $pcomp1d;
$sqft1 = str_replace( ',', '', $b1 );

if( is_numeric( $sqft1 ) ) {
    $b1 = $sqft1;
}

$a2 = $pcomp2f;
$price2 = str_replace( ',', '', $a2 );

if( is_numeric( $price2 ) ) {
    $a2 = $price2;
}
$b2 = $pcomp2d;
$sqft2 = str_replace( ',', '', $b2 );

if( is_numeric( $sqft2 ) ) {
    $b2 = $sqft2;
}

$a3 = $pcomp3f;
$price3 = str_replace( ',', '', $a3 );

if( is_numeric( $price3 ) ) {
    $a3 = $price3;
}
$b3 = $pcomp3d;
$sqft3 = str_replace( ',', '', $b3 );

if( is_numeric( $sqft3 ) ) {
    $b3 = $sqft3;
}
$ppsqft1 = ROUND($price1 / $sqft1);
$ppsqft2 = ROUND($price2 / $sqft2);
$ppsqft3 = ROUND($price3 / $sqft3);

$ppsav = ROUND((($ppsqft1 + $ppsqft2 + $ppsqft3)/3));

$b4 = $property_area;
$parea = str_replace( ',', '', $b4 );

if( is_numeric( $parea ) ) {
    $b4 = $parea;
}
$ehvp = $ppsav * $parea;
$homevalue = number_format($ehvp, 0, '.', ',');
echo '$' . $homevalue; ?>

And functions --

<?php if (class_exists('MRP_Multi_Rating_API')){ MRP_Multi_Rating_API::display_rating_result(array('rating_item_ids' => 2, 'show_count' => false, 'result_type' => 'value_rt', 'no_rating_results_text' => 'N/A'));} ?>

So how can I get this to work with ng-view and my partials templates?

UPDATE

This is what my current setup looks like -- First I have a page template called search-results.php -

<?php
/* Template Name:Search Results */ ?>
    <!DOCTYPE html>
<html>
<head>
    <base href="<?php $url_info = parse_url( home_url() ); echo trailingslashit( $url_info['path'] ); ?>">
        <title>Search</title>
<link rel="stylesheet" type="text/css" href="/style.css" media="print" />
<script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
<script src="//code.jquery.com/jquery-migrate-1.3.0.min.js"></script>
    <?php wp_head(); ?>
</head>
<body>
    <div id="page" ng-app="app">
        <header>
            <h1>
                <a href="<?php echo home_url(); ?>">Search</a>
            </h1>
        </header>
  <?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>

   <div ng-Cloak ng-controller="MyController" class="my-controller">
<div ng-view></div></div>
  <?php endwhile; ?>
 <?php endif; ?>
<?php rewind_posts(); ?>
<div ng-controller="OtherController" class="other-controller">
          <div class="text-center">
          <dir-pagination-controls boundary-links="true" on-page-change="pageChangeHandler(newPageNumber)" template-url="/partials/dirPagination.tpl.html"></dir-pagination-controls>
          </div>
        </div>
        <footer>
            &copy; <?php echo date( 'Y' ); ?>
        </footer>
    </div>
<script>
function getdata($scope,$http){
$http.get("/wp-json/posts?type=property")
.success(function(data)
{$scope.result = data;}
);
}
</script>

    <?php wp_footer(); ?>
</body>
</html>

Then my app script without calling the php file as a partial --

var app = angular.module('app', ['ngRoute', 'ngSanitize', 'angularUtils.directives.dirPagination'])

function MyController($scope) {


$scope.currentPage = 1;

$scope.pageSize = 2;

$scope.posts = [];


$scope.pageChangeHandler = function(num) {

console.log('search page changed to ' + num);
  };
}



function OtherController($scope) {

$scope.pageChangeHandler = function(num) {

console.log('going to page ' + num);
  };
}



app.config(function(paginationTemplateProvider) {
    paginationTemplateProvider.setPath('/partials/dirPagination.tpl.html');
});

app.config(function($routeProvider, $locationProvider) {
    $locationProvider.html5Mode(true);

    $routeProvider
    .when('/search-results', {
        templateUrl: myLocalized.partials + 'main.html',
        controller: 'Main'
    })
    .when('/:ID', {
        templateUrl: myLocalized.partials + 'content.html',
        controller: 'Content'
    });
})
app.controller('Main', function($scope, $http, $routeParams) {
    $http.get('wp-json/posts?type=property').success(function(res){
        $scope.posts = res;
    });
})
app.controller('Content', function($scope, $http, $routeParams) {
    $http.get('wp-json/posts?type=property/?filter["posts_per_page"]=25&filter["orderby"]=date&filter["order"]=desc/' + $routeParams.ID).success(function(res){
        $scope.post = res;
    });
});


app.controller('MyController', MyController);
app.controller('OtherController', OtherController);

Then the partial file will be a search.php file with the functions and code shown in my original question.

2 Answers 2

1

What works is calling WP's Ajax endpoint. Note that it is not the address of your partial, but the fixed WP address for all AJAX calls

wp_localize_script('handle', 'myLocalized', array(
     'ajaxUrl' => admin_url( 'admin-ajax.php' )
) );

provides the endpoint adress in the JS object myLocalized. You send all requests to that page and refer to your partials only as GET parameters:

$routeProvider.when('/search-results', {
    templateUrl: myLocalized.ajaxUrl + '?action=get_my_partial&partial=' + 'main';
    controller: 'Main'
}).when('/:ID', {
    templateUrl: myLocalized.ajaxUrl + '?action=get_my_partial&partial=' + 'content';
    controller: 'Content'
});

All calls to admin-ajax.php with a parameter {action} get routed to actions named wp_ajax_{action} (for logged in users) or wp_ajax_nopriv_{action} (for users not logged in). On the server side, you can hook up to them and include your partials:

function get_the_partials () {
    switch ( $_GET['partial'] ) {
        case 'main':
            // check authorization
            include plugin_dir_path(__FILE__) .'/partials/main.html';
            break;
        case 'content':
            // check authorization
            include plugin_dir_path(__FILE__) .'/partials/content.html';
            break;
        default:
            wp_die( 'Nothing found.' );
            break;
    }
    die();
}
add_action( 'wp_ajax_nopriv_get_my_partial', 'get_the_partials' );
add_action( 'wp_ajax_get_my_partial', 'get_the_partials' );

This way, WordPress is fully initialised and can handle any functions normally available. (Well, outside the loop.)

Since you handle selecting specific posts from the controller and through the wp-json endpoint, that should do it.

8
  • 1
    This admin AJAX endpoint works (if you're logged in) but it might be better to use a custom endpoint - codex.wordpress.org/Rewrite_API/add_rewrite_rule - api/partials/myPartial. Also you should never directly use the $_GET... especially on an include. At a high level this code looks workable but it's a security nightmare.
    – jgraup
    Commented Feb 15, 2016 at 3:11
  • @jgraup Obviously. The $_GET was only used for demonstration purposes here.
    – ccprog
    Commented Feb 15, 2016 at 13:59
  • I can clearly see that. I'm not sure others will always see it the same way. For that reason I would suggest a non-logged in AJAX request in your example and mention sanitization at a minimum.
    – jgraup
    Commented Feb 15, 2016 at 14:16
  • Edited, that should be a bit more realistic.
    – ccprog
    Commented Feb 15, 2016 at 15:05
  • Sorry, don't mean to be 'that guy' but 'no_priv' is non-logged in users while the alternate is just logged in users. In this case you should not use an anonymous function and you should subscribe both hooks to the common function. See codex.wordpress.org/AJAX_in_Plugins
    – jgraup
    Commented Feb 15, 2016 at 15:38
1

1 ) URL Rewrite

I'm pretty partial to url rewrites. They're easy to setup, customize, and you get access to all of WordPress.

In the class below you can see that rewrites are added (you only need to flush the rewrite rules once) and if a portion of the url matches then sections can be used as dynamic variables (regEx -> query_vars). The fun part is that you can kill the request anytime to return JSON data, or let WordPress do it's thing in addition to specifying the template page to use.

So we're going to turn a front-end request for:

example.com/api/angular/partial/custom

into something we can use internally as:

example.com/index.php?__api_angular=1&partial=custom

Anything extra should be added in the regEx when registering the rewrite -- but I think this gets the point across.

This class can be added anywhere really (functions.php / plugins). It instantiates itself and hooks what it needs in init(). Personally, I like using a class because the code is contained and it won't conflict if the class is defined somewhere else... well, it won't recreate the class if it's already defined.

<?php

if ( ! class_exists( 'AngularEndpoint' ) ):

    class AngularEndpoint {
        const ENDPOINT_QUERY_NAME  = 'api/angular';
        const ENDPOINT_QUERY_PARAM = '__api_angular';

        // WordPress hooks

        public function init() {
            add_filter( 'query_vars', array ( $this, 'add_query_vars' ), 0 );
            add_action( 'parse_request', array ( $this, 'sniff_requests' ), 0 );
            add_action( 'init', array ( $this, 'add_endpoint' ), 0 );
        }

        // Add public query vars

        public function add_query_vars( $vars ) {

            // add all the things we know we'll use

            $vars[] = static::ENDPOINT_QUERY_PARAM;
            $vars[] = 'partial';
            $vars[] = 'filter';
            $vars[] = 'type';

            return $vars;
        }

        // Add API Endpoint

        public function add_endpoint() {
            add_rewrite_rule( '^' . static::ENDPOINT_QUERY_NAME . '/partial/([^/]*)/?', 'index.php?' . static::ENDPOINT_QUERY_PARAM . '=1&partial=$matches[1]', 'top' );

            //////////////////////////////////
            flush_rewrite_rules( false ); //// <---------- REMOVE THIS WHEN DONE
            //////////////////////////////////
        }

        // Sniff Requests

        public function sniff_requests( $wp_query ) {
            global $wp;

            if ( isset(
                $wp->query_vars[ static::ENDPOINT_QUERY_PARAM ],
                $wp->query_vars[ 'partial' ] ) ) {
                $this->handle_partial_request(); // handle it
            }
        }

        // Handle Requests

        protected function handle_partial_request() {
            global $wp;

            $partial_requested = $wp->query_vars[ 'partial' ];

            switch ( $partial_requested ) {

                // example.com/api/angular/partial/ping
                case 'ping':
                    wp_send_json_success( array (
                        'message' => 'Enjoy your partial', 'partial' => $partial_requested,
                    ) );
                    break;

                // example.com/api/angular/partial/custom
                case 'custom':
                    add_filter( 'template_include', function( $original_template ) {
                        return __DIR__ . '/custom.php';
                    } );
                    break;

                // example.com/api/angular/partial/search
                case 'search':
                    add_filter( 'template_include', function( $original_template ) {
                        return get_template_directory() . '/search.php';
                    } );
                    break;
                default:
                    wp_send_json_error( array ( 'message' => 'Invalid Request' ) );
            }
        }
    }

    $wpAngularEndpoint = new AngularEndpoint();
    $wpAngularEndpoint->init();

endif; // AngularEndpoint

custom.php Used as the template for example.com/api/angular/partial/custom

<html>
<body>
<h1>Custom Stuff</h1>
<h2><?php echo "PHP Stuff" ?></h2>
<?php
$posts = get_posts();
?>
<ul>
<?php
    foreach ( $posts as $post ) {
        echo '<li>' . $post->post_title . '</li>' . PHP_EOL;
    }
?>
</ul>
</body>
</html>

2 ) Rest Route

Another option, since you're already using wp-json, is to make a custom rest route. There is less setup required here which is a plus.

<?php

if ( ! class_exists( 'AngularJSONEndpoint' ) ):

    class AngularJSONEndpoint {
        const ENDPOINT_NAMESPACE  = 'namespace/v2';

        /**
         * Initialize WordPress hooks
         */
        public function init() {
            add_action( 'init', array ( $this, 'add_endpoint' ), 0 );
        }

        /**
         * Add JSON API Endpoint
         */
        public function add_endpoint() {

            add_action('rest_api_init', function () {

                // http://example.com/wp-json/namespace/v2/angular?partial=custom

                register_rest_route( static::ENDPOINT_NAMESPACE, '/angular', array (
                    'methods'             => 'GET',
                    'callback'            => array($this, 'wp_json_namespace_v2__angular'),
                    'permission_callback' => function (WP_REST_Request $request) {
                        return true;
                    }
                ));
            });

            flush_rewrite_rules(true); // FIXME: <------- DONT LEAVE ME HERE
        }

        /**
         * Handle the endpoint
         * @param $request
         *
         * @return WP_REST_Response
         */
        function wp_json_namespace_v2__angular($request)
        {
            // json-api params

            $parameters = $request->get_query_params();

            // check for partial requests

            if(isset($parameters['partial'])){
                switch($parameters['partial']) {
                    case 'custom':
                        require __DIR__ . '/custom.php';
                        die();
                }
            }

            // return results

            $data = array(
                'success' => false,
                'message' => 'Bad Request'
            );

            return new WP_REST_Response($data, 400);
        }
    }

    $wpAngularJSONEndpoint = new AngularJSONEndpoint();
    $wpAngularJSONEndpoint->init();

endif; // AngularJSONEndpoint
7
  • For the rest route method, where would I put the class function code, in functions.php?
    – 730wavy
    Commented Mar 18, 2016 at 8:58
  • If you want it to stay with your theme then functions will work. Generally a plugin will be better because you can use with any theme. To test that it's working you can add to functions.php but eventually move all this Angular stuff into its own plugin.
    – jgraup
    Commented Mar 18, 2016 at 10:19
  • Sorry about that I've been busy and had to put things on hold. Im looking into it now and for your second method, my partial files is located at -- /public_html/wp-content/themes/child/partials then exactly what part of your code would I need to modify to direct it correctly?
    – 730wavy
    Commented Apr 8, 2016 at 16:21
  • child is the name of your current theme? and /partials are where they are located? Take a look at codex.wordpress.org/Function_Reference/get_template_directory to point to a file in your own directory and replace the line require __DIR__ . '/custom.php'
    – jgraup
    Commented Apr 8, 2016 at 22:04
  • Ok what about in my script -- app.config(function($routeProvider, $locationProvider) { $locationProvider.html5Mode(true); $routeProvider .when('/search-results', { templateUrl: myLocalized.partials + 'main.html', controller: 'Main' })
    – 730wavy
    Commented Apr 9, 2016 at 2:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.