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

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By fruid
      could someone help me find the javascript bug?
      https://codepen.io/bbblgmsp/pen/LYxWJJa
      or here:
      https://foobar.roofaccess.org/carousell/
      I want it to be NOT draggable on desktop but draggable on mobile devices. Also, on mobile, the element that I drag into focus should automatically be active, but on desktop, only when I click it.
      It works fine on desktop, but when you drag on mobile, after the dragged element is active and you click it, it switches back and forth between this and the previous element (assigns the active class to the previous element).
      I already tried to find help elsewhere, since this is not directly PW related but only frontend, but it's hard to find support or a community for uikit. PW seems to be using it quite a lot though…
      Thanks for help
    • By franciccio-ITALIANO
      Hello to all. I would like to create an app. So I need to learn at least one programming language. I got informed online, and discovered that javascript with node.js, is the revolution of recent years, because it's faster than php. I wonder: if I develop an app with javascript and with a javascript framework (e.g. Meteor), is there a way to integrate processwire work? I know that processwire supports the transformation of the site into an application, but would it be as simple as Meteor? With the Meteor framework I have my app online in 10 minutes, and without even knowing javascript! (Knowing javascript would serve to personalize it). I should then install the app in a SUB-DOMAIN. If I study php, instead, and if I use a php framework (e.g. Laravel), how long does it take to have my first working app? Is it easy to process Laravel's components? Is writing forms for processwire apps with php a very complex job? Is it better to use Meteor and start with javascript? What would you recommend?
    • By ngrmm
      My clients wants a modal to show up on every page. But when a user clicks inside the modal -> a session-cookie is set and the modal gets a class.
      // user clicks on modal button $('.modal_button').click(function(){ // 1. set PW session cookie // 2. toggle class $('.modal').toggleClass('off'); }); I know how to set a cookie on page-load via PW-API. But the click on the modal button does not force a page-load. So i have to set the cookie through javascript. Is there a way to do that?
    • By ICF Church
      Hi 👋
      Anyone else having this problem?
      Requirements:
      - Repeater (matrix & normal) with mutlilanguage fields (text, textarea…) 
      - Backend language set to something other than default (ie. German) 
      Reproduce:
      - Add a new repeater Item (ajax, I found no way to possible to disable it with matrix)

      (Notice how the default language tab is active instead of the backend language…)
      - Write something into the (default language) field
      - Try to save, if field is required, this will not work. If not required, then when reloading, the content will be inside the backend language field, instead of the default language field who was (presumably) active
      Analysis:
      When  loading  a new repeater element with ajax, the default langue tab is active, but the backend language inputfield is visible (with no visual indication). When writing into the field, it will populate the backend language. When manually clicking on the default language tab (which is already active), the field will switch to the actual default language field (which is [now] empty) (that can now be populated…)
      Also Notice, the labels of the elements to be added are in default language as well instead of the translated label (images instead of Bilder)…
      ProcessWire 3.0.148, Profields 0.0.5…
      Is it my system configuration, or does anyone else have the same issue? This is a screen recording of the problem:
      Issue: https://github.com/processwire/processwire-issues/issues/1179

      Screen Recording 2020-02-25 at 14.18.31.mov
    • By adisharma
      Hello Everyone, I am new in this community and I am learning typescript but my friend was suggested to me go with javascript profile because it is most popular scripting language for many web projects and huge community support with lots of documentation and support for solving issues. Can anyone clear this point which one is better for future points of view typescript or javascript?
×
×
  • Create New...