Jump to content

How to work with AJAX driven content in ProcessWire


ryan

Recommended Posts

Turns out I was using a selector that didn't work... I basically used the same one I use for regular search. In the end my selector just needed to define the parent, that was all.

It all works nicely now. I've added a little LazyCron task too: I save the JSON in certain intervals to a (hidden) PW page/field, and only query this JSON data with JS. It really speeds things up. Otherwise (with browser cache cleared) I had to wait a few seconds till the suggestions were loaded.

Thanks again for your help, suggestions + patience @AndZyk

  • Like 5
Link to comment
Share on other sites

Glad to hear it works now.

1 hour ago, dragan said:

I've added a little LazyCron task too: I save the JSON in certain intervals to a (hidden) PW page/field, and only query this JSON data with JS. It really speeds things up. Otherwise (with browser cache cleared) I had to wait a few seconds till the suggestions were loaded.

If your site doesn't frequently change, there is an cache option available in the Typeahead plugin:

Quote

cache: false,           // Improved option, true OR 'localStorage' OR 'sessionStorage'

But use whatever works best for you. ;)

  • Like 3
Link to comment
Share on other sites

On 8/26/2017 at 1:45 AM, dragan said:

I've added a little LazyCron task too: I save the JSON in certain intervals to a (hidden) PW page/field, and only query this JSON data with JS. It really speeds things up. Otherwise (with browser cache cleared) I had to wait a few seconds till the suggestions were loaded.

@dragan - Can you elaborate more on how you did this?  I would like to implement something like this on my sites.

Link to comment
Share on other sites

3 hours ago, gmclelland said:

@dragan - Can you elaborate more on how you did this?  I would like to implement something like this on my sites.

If I understand the needs correctly, I think this is a perfect use case for WtireCache (https://processwire.com/blog/posts/processwire-core-updates-2.5.28/#wirecache-upgrades). I use this to store the results of a complex set of selector queries that serve JSON to an AngularJS app.

  • Like 1
Link to comment
Share on other sites

@gmclelland Basically, what I did is installing this module: https://processwire.com/api/modules/lazy-cron/

Then I added this one line at the very bottom of my "search-input" template (the only place I need to ever access that index JSON):

wire()->addHook('LazyCron::everyHour', null, 'cronAutocompleteJSONCache');

In file _cron.php, that I

include_once("./_cron.php");

in my search-input template, I have this:

<?php namespace ProcessWire;

function cronAutocompleteJSONCache(HookEvent $e) {

    $selector = "parent=1041, has_parent!=2";
    $matches = wire('pages')->find($selector);

    foreach ($matches as $match) {
        $result = array(
            "title" => htmlspecialchars_decode($match->title),
            "year" => $match->year,
            "project_desc_short" => htmlspecialchars_decode($match->project_desc_short)
        );
        $results[] = $result;
    }

    $cacheHolderPage = wire('pages')->get(10029);
    $cacheHolderPage->setOutputFormatting(false);
    $cacheHolderPage->autocomplete_json = json_encode($results);
    $cacheHolderPage->last_update = date('d.m.Y h:i:s');
    $cacheHolderPage->save();

}

Of course, I could have also saved the JSON on the file-system, and check the timestamp, and decide on every request if the static JSON is older or newer than the PW-content, and load/generate one or the other, but for my needs, this seemed like the easiest and most straightforward solution.

 

PS

the field last_update I have only added for myself to see if it really works. I made it a "read-only" field in the settings. (not editable)

  • Like 1
Link to comment
Share on other sites

  • 4 months later...

Hi all,

slightly different issue here - I'm working with a search form (multiple fields, also arrays) to filter the list of items showed in the same page. I've implemented ajax for filtering items as-you-type or as-you-select-options, which is working just fine.

The only problem is the pagination: while ajax pulls items correctly within the selected container, the pagination keeps all pages as no filter form were submitted. In fact, pagination and everything else works perfectly if the form is submitted, but by getting items via ajax without "click form submit" the pagination keeps all items regardless the applied filter . here's selected code part:

$("#formNews").on('change', function ajaxWorks(event){
    var form = $(this);
    $.ajax({
        type: form.attr('method'),
        url: form.attr('action'),
        data: $('#formNews').serialize(),
        success: function(data) {
            var data = $(data).find('#mediaGrid').html();
            data = '<div id="mediaGrid" class="content-current" uk-grid>' + data + '</div>'; // div container
            // remove old content
            $('#mediaGrid.content-current').remove();
            // scroll to the top of the page
            $('html, body').animate({ scrollTop: 0 }, 'slow');
            // append new content to the page
            $('#colMedia').append(data);
        }
    });
    event.preventDefault();
});

The div container contains also the pagination function, so once ajax successes, it pulls both items and the pagination into the div container.

Am I forced to submit the form even though I am using ajax? thanks!

EDIT: The pagination for the ajax pulled items is ok, and it works if I click on the second/next page (without form submission). Since I am using IAS Infinite Ajax Scroll, it seems that the problem is when IAS clicks on the next page ancor tag, as no URL is updated and therefore the pagination thing gets messed up..

Link to comment
Share on other sites

I'm not quite sure I understand... what is #colMedia?

Maybe use another variable name than data in your success function? i.e.

var d = $(data).find('#mediaGrid').html();

Do you add items with filtering, or remove? (remove would be the behavior that Lister Pro uses - i.e. show fewer items as you add more filter conditions)

Link to comment
Share on other sites

56 minutes ago, dragan said:

I'm not quite sure I understand... what is #colMedia?

#colMedia is the parent div of the #mediaGrid items container, I'm calling it to append the #mediaGrid div to the #colMedia div after ajax success.

I've found that probably is an IAS problem, and I'm testing some reinitialize options without success, like this

jQuery.ias().reinitialize();
//or
ias.destroy(); ias.bind();

 

Link to comment
Share on other sites

  • 1 year later...

@AndZyk Hey this Autocomplete typeahead search is not working for me.  

$.typeahead is not a function this kind of error I am getting where's the problem? 

@AndZyk Hey this Autocomplete typeahead search is not working for me.  

$.typeahead is not a function this kind of error I am getting where's the problem? 

Link to comment
Share on other sites

  • 8 months later...

Hi,

I'm trying the simple json return example, all I've done is change the url to the page I'm requesting:

<ul id='info'></ul>
<script type='text/javascript'>
    var url = '/wishlist/func_wishpage/'; // this is homepage, so replace '/' with page URL you want to load JSON from
    $(document).ready(function() {
        $.getJSON(url, function(data) {
            $.each(data, function(key, value) {
            $("#info").append("<li>" + key + ": " + value + "</li>");
        });
    });
});
</script>

The page I'm requesting, with just some dummy info:

<?php

if($config->ajax) {

// this is an ajax request, return basic page information in a JSON string
$json = array(
    'id' => 3333,
    'something' => "else",
    );
echo json_encode($json);
return;

}

It does not return any values, am I missing something?

 

Link to comment
Share on other sites

  • 7 months later...

So this last topic just above me - where the person trying to use multiple dropdowns to send ajax filters to a page output container. I think this is what I am trying to figure out. I've got someone interested in moving from WordPress to Processwire but they really like the FacetWP plugin functionality and so I'm looking at techniques for replicating that process.

If you've never seen the plugin: https://facetwp.com/

It seems very similar to what Ryan uses in the bike tours website. I do believe there is some caching involved in terms of result set combinations? - I haven't looked deeply into the FacetWP code, but my impression is that it runs something like this:

  • Grab an initial catalog defined by a search selector(s) definition - this would be provided by the configuration - what pages to list in the results and then which page references fields to be able to filter on.
  • Grab distinct page reference fields and selected values for those - package them into initiall object to output
  • Push those template output items to a paginated search results template and a dynamically generated checkbox fieldset, or multiple selection box, etc navigation template.
  • Live on the client, when an option is selected, send the new selector modifications back to the original search results template - adding the option to the original property selector
  • At this point, there is an option branch that facet employs - you can either keep the full list of possible options and then grey out the options as unselectable where no valid combinations exist, or you can hide the options that are no longer valid - I don't believe there is any interaction between the typical wordpress search box and the facet filters - but I don't see why you couldn't have a selector title tie-in, for example - though precaching result combinations goes completely out the window there...
  • Inject the updated options selectors template and paginated results.

Does this sound about right? So I would want to call a navigation template/fieldset and the paginated result set/controls with ajax and then update both templates with ajax?

I don't really know how the facetWP plugin degrades without javascript - I think it may just show the full list of checkboxes as a control that uses a plain form destination with getstring parameters passed. I suppose it would just go back to the search page results template!

On the server side, could I just create a single search results output template that detect whether it should return a full page or just the page collection and pagination number depending on whether the request is ajax or not? I supposed I'd still need to have a separate filter generator template? Maybe it could all get loaded by the same search results/filter form template?

Link to comment
Share on other sites

  • 2 years later...

I need to make two separate AJAX calls method POST on the same template/page. Somehow the first one works fine but the second just won't send the data. Any ideas?

// template file

<?php namespace ProcessWire; 
if ($config->ajax) :
    $action = $input->post->action;
    $response = array();
    if ($action == 'sendFormData') : // first ajax call uses POST method
        // some logic
    else : // second ajax call uses GET method
        // some logic
        $response['action'] = $action;
        echo json_encode($response);
    endif;
    return $this->halt();
else : ?>

    <div id="content">
        HELLO WORLD
    </div>

<?php endif; ?>

 

// javascript file

// first call
sendFormData()

// later 
secondAJAX()

function sendFormData(formData) {
    var firstAJAXXHR = new XMLHttpRequest();
    firstAJAXXHR.onreadystatechange = function () {
        if (firstAJAXXHR.readyState !== 4) return;
        if (firstAJAXXHR.status >= 200 && firstAJAXXHR.status < 300) {
            let response = firstAJAXXHR.responseText;
            response = JSON.parse(response);
            console.log('hello AJAX #1');
            console.log(response.action); // sendFormData
        }
    };
    firstAJAXXHR.open('POST', '', true);
    firstAJAXXHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    firstAJAXXHR.send(formData);
}

function secondAJAX() {
    var secondAJAXXHR = new XMLHttpRequest()
    secondAJAXXHR.onreadystatechange = function () {
        if (secondAJAXXHR.readyState !== 4) return;
        if (secondAJAXXHR.status >= 200 && secondAJAXXHR.status < 300) {
            let response2 = secondAJAXXHR.responseText
            response2 = JSON.parse(response2);
            console.log('hello AJAX #2')
            console.log(response2.action); // null but why? $%!#&
        }
    }
    // var request = {'action' : 'capturePayment'};
    var request = 'action=sendEmail';
    secondAJAXXHR.open('POST', '', true);
    secondAJAXXHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    secondAJAXXHR.send(request);
}

 

Link to comment
Share on other sites

hum.. looks like the second call uses POST too:

secondAJAXXHR.open('POST', '', true);

do you fill the $action variable in the "some logic" code?

// some logic
$response['action'] = $action;

if not, will be empty for a GET request

  • Like 1
Link to comment
Share on other sites

SOLVED!

second post uses POST too, the comment where it says GET is a typo.

Anyways, I think to have figured it out, as often, the problem is with the headers. It needs both:

secondAJAXXHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
secondAJAXXHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

This seems so random to me, at least until I understand this headers stuff.

That said, will that cause issues in different browsers, clients and whatnot?

  • Like 1
Link to comment
Share on other sites

I think you don’t need to manually set the Content-Type header if you send a URLSearchParams object:

secondAJAXXHR.send(new URLSearchParams(request));

(no support in the old IE though)

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...