Jump to content

Take Javascript value and reload content with AJAX


Liam88
 Share

Recommended Posts

Hi,

I'm really struggling with this as it's something not in my wheelhouse. I'm creating a blog style page (a grid of cards) which has attributes.

I have a snip of javascript which grabs values from checkboxes which are put into a value like the below:

?content=static_video&channel=facebook-ads_instagram-ads

document.querySelector("form").onsubmit=ev=>{
	ev.preventDefault();
	let o={};
	ev.target.querySelectorAll("[name]:checked").forEach(el=>{
	(o[el.name]=o[el.name]||[]).push(el.value)})
	console.log(location.pathname+"?"+
	Object.entries(o).map(([v,f])=>
	v+"="+f.join("_")).join("&")
	);
	document.location.href = location.pathname+"?"+
	Object.entries(o).map(([v,f])=>
	v+"="+f.join("_")).join("&");
}

As I'm currently refeshing the page on button click with those values the end result includes the location but can easily remove this.

I then use this value in "input->get" to get the values which I then append to a find() rule. See code below:

$selector = "template='adbank_pages',sort=published,include=all,status!=hidden";

// Get the channel and content inputs
$channel = $input->get->channel;
$content = $input->get->content;

if($channel){
  // Grab the channel string, explode into an array for checkbox checking and then replace _ with | to create or rules in the selector.
  $chanArray = explode("_", $channel);
  $chan = $channel = str_replace('_', '|', $channel);
  $selector = $selector .= ",ab_channels=$chan";
}
if($content){
  // Grab the content string, explode into an array for checkbox checking and then replace _ with | to create or rules in the selector.
  $contArray = explode("_", $content);
  $cont = $content = str_replace('_', '|', $content);
  $selector = $selector .= ",ab_content=$cont";
}
if($input->get){
	// If a valid input result
	$all = $pages->find($selector);
	}
}else{
	// If no input show them all
	$all = $page->children("template='adbank_pages',sort=-published,include=all,status!=hidden");
}
$items = $all->find("limit=12");
// Limit the output and use pagination

As mentioned above I currently refresh the page to adjust the $selector filter within the $all with a fallback $all if there are no results.

I know I need to use AJAX to filter the content without refresh but I am really struggling with the set up. I have read multiple posts including the original by Ryan but still confused.

If anyone can direct/help on this it would be appreciated.

Thank you

Link to comment
Share on other sites

  • 3 weeks later...
Quote

I know I need to use AJAX to filter the content without refresh but I am really struggling with the set up. I have read multiple posts including the original by Ryan but still confused.

The problem here is that it depends a lot on your setup, i.e. which output strategy you're using and how your site is set up etc. 🙂

If you're using simple direct output (templates/template_name.php directly renders output) then you could do something as simple as this:

<?php namespace ProcessWire;

// finding results goes here

if ($config->ajax) {

    // render results without page "frames", i.e. just the list of cards

    // ... and when done, halt the rendering process:
    $this->halt();
}

// normal page output

Now you just need to trigger an AJAX request to the same page, read the output, and then replace the part of the page content that you wish to be "dynamically filtered" with new content. Note that for $config->ajax to work, your AJAX request needs to include the "X-Requested-With: XMLHttpRequest" header; jQuery adds this automatically, but if you're using raw XMLHttpRequest, Fetch API, or any other approach to trigger the request then you'll likely have to add that header yourself.

More examples for AJAX loading content (including alternative approach, where instead of content you return JSON and then render output in JavaScript) can be found from this thread:

Loosely related, but a couple of issues I spotted in your sample code:

  • You're passing $chan and $cont values to the selector string unfiltered. This is not a good idea: visitors could pass in characters that break the query, or even rewrite the query. Always run user-provided values through Sanitizer methods ($sanitizer->selectorValue()) is a good default, but if you're expecting integers then use $sanitizer->int(), etc.
  • Since you're first finding all results and then limiting them, you will run into performance issues if there's a lot of data. As a general rule of thumb always include a limit in your initial selector — this way the limit is applied in the database, which is much more efficient.
  • Like 1
Link to comment
Share on other sites

5 hours ago, teppo said:

The problem here is that it depends a lot on your setup, i.e. which output strategy you're using and how your site is set up etc. 🙂

If you're using simple direct output (templates/template_name.php directly renders output) then you could do something as simple as this:

<?php namespace ProcessWire;

// finding results goes here

if ($config->ajax) {

    // render results without page "frames", i.e. just the list of cards

    // ... and when done, halt the rendering process:
    $this->halt();
}

// normal page output

Now you just need to trigger an AJAX request to the same page, read the output, and then replace the part of the page content that you wish to be "dynamically filtered" with new content. Note that for $config->ajax to work, your AJAX request needs to include the "X-Requested-With: XMLHttpRequest" header; jQuery adds this automatically, but if you're using raw XMLHttpRequest, Fetch API, or any other approach to trigger the request then you'll likely have to add that header yourself.

More examples for AJAX loading content (including alternative approach, where instead of content you return JSON and then render output in JavaScript) can be found from this thread:

Loosely related, but a couple of issues I spotted in your sample code:

  • You're passing $chan and $cont values to the selector string unfiltered. This is not a good idea: visitors could pass in characters that break the query, or even rewrite the query. Always run user-provided values through Sanitizer methods ($sanitizer->selectorValue()) is a good default, but if you're expecting integers then use $sanitizer->int(), etc.
  • Since you're first finding all results and then limiting them, you will run into performance issues if there's a lot of data. As a general rule of thumb always include a limit in your initial selector — this way the limit is applied in the database, which is much more efficient.

Hey,

thank you for the reply and direction.

I'm going to take a moment to digest the ajax part.

In the meantime I did update the input part to the below just after doing this post. It would be good to get your view whether this is a better way.

//Default selector to get ALL products
$baseSelector = "template='adbank_pages',sort=-created,include=all,limit=12";
$selector = "template='adbank_pages',sort=-created,include=all,limit=12";

// Get the inputs, sanitize and whitelist. Then create an array, then replace _ for | in array for selector filter. Output selector 

if($input->get->channel){
	$channel = $input->get->text('channel');
	$input->whitelist('channel', $channel);
	$chanArray = explode("_", $channel);
	$chan = $channel = str_replace('_', '|', $channel);
	$selector = $selector .= ",ab_channels=$chan";
}
if($input->get->content){
	$content = $input->get->text('content');
	$input->whitelist('content', $content);
	$contArray = explode("_", $content);
	$cont = $content = str_replace('_', '|', $content);
	$selector = $selector .= ",ab_content=$cont";
}
if($input->get->brand){
	$brands = $input->get->text('brand');
	$input->whitelist('brand', $brands);
	$brandArray = explode("_", $brands);
	$brand = $brands = str_replace('_', '|', $brands);
	$selector = $selector .= ",ab_brand=$brand";
}

 

Link to comment
Share on other sites

On another sidenote, in this part of your code

if ($input->get){
...
}else {
...
}

$input->get will always return true, even if you do not have any GET parameters in your URL because $input->get returns an WireInputData object. You should use

if(count($input->get)) // returns 0 if there are no URL params

instead.

  • Like 1
Link to comment
Share on other sites

4 minutes ago, gebeer said:

On another sidenote, in this part of your code

if ($input->get){
...
}else {
...
}

$input->get will always return true, even if you do not have any GET parameters in your URL because $input->get returns an WireInputData object. You should use

if($input->get->data) // returns null if there are no URL params

instead.

Amazing, thank you. 

I noticed that when I first did it and changed it to check if count was > etc.

Now changed to the above.

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
 Share

×
×
  • Create New...