Jump to content

Suggestions on implementing a Pinterest like save button


Liam88
 Share

Recommended Posts

Hello all,

Looking for input on the best way to implement a Pinterest like save button.

Aim:

I want logged in users to be able to save posts to their profile for future accessibility. 

I would like them to be able to give it a subject, in other words the ability to bucket them into groups.

Does anyone have a suggestion or an example of something similar?

I would say my main hurdle is implementing a front end button that would then add a page id into the back end. Guessing that db element would have a page ID and user ID so I can then filter page IDs relevant to User ID.

TIA.

Link to comment
Share on other sites

You would need to create a role, say 'frontend_user' or some such. (Can't use 'user' because $user is a thing already.)

Then do something like 

if($user->hasRole('frontend_user')) {
  // show input form
}

See https://processwire.com/api/ref/user/has-role/

Then https://processwire.com/api/ref/pages/add/ or https://processwire.com/api/ref/pages/new/ should help. You don't need to worry about database IDs and such - PW's API deals with all that.

Link to comment
Share on other sites

26 minutes ago, DaveP said:

You would need to create a role, say 'frontend_user' or some such. (Can't use 'user' because $user is a thing already.)

Then do something like 

if($user->hasRole('frontend_user')) {
  // show input form
}

See https://processwire.com/api/ref/user/has-role/

Then https://processwire.com/api/ref/pages/add/ or https://processwire.com/api/ref/pages/new/ should help. You don't need to worry about database IDs and such - PW's API deals with all that

Thanks @DaveP

 

I'm using loginregisterpro so I have a lot covered with that.

I'll take a look at the links you popped in.

appreciate the direction.

Link to comment
Share on other sites

  • 3 weeks later...
On 9/1/2022 at 10:30 AM, flydev ?? said:

@Liam88 you might try and ask @ryan if you could achieve it with the Pro Module `Likes`:

 

 

Thanks @flydev ??, it looks like it may by this line:

"This enables you to have a separate page (or perhaps a sidebar on every page) that [for example] shows the user bookmarks of pages they liked"

However, I'm not sure it would match the full desired behaviour with the below:

"FieldtypeLikes is not connected with the user system, and may be used anonymously via any page on your site."

I want the liked page to be connected to the user so it can be outputted in a "profile" area. Think Pinterest or even more relevant "follow" on here.

So I'm looking to.

Have a save button on a page.

  • If the user is logged in then save the page
  • if not then fire a modal with the LRP register execution. Probably could be done with a check whether a user is logged in.

However, I was hoping there would be a simple way to just have a connection between user id and page id.

Maybe something like clicking save would add the page id to a repeater but feels quite clumsy and probably slow.

Overall, not sure on the best practice or best direction to achieve this. The joys of using a side project to learn more.

Any thoughts from your side @ryan on whether likes module would work is appreciated.

Thanks

Link to comment
Share on other sites

18 hours ago, Liam88 said:

"FieldtypeLikes is not connected with the user system, and may be used anonymously via any page on your site."

I understand, and my point to suggest you to ask @ryan directly if his pro module can fit your needs, is because ryan is used to give more flexibility instead of locking the user to a finished functionality. I mean, there is maybe some hooks that can be used to link things to an user. Without speaking about that you benefit ryan's code and insight ?

Anyway, I will might buy it for a project and could give more feedbacks. Stay tuned.

  • Like 1
Link to comment
Share on other sites

1 hour ago, flydev ?? said:

I understand, and my point to suggest you to ask @ryan directly if his pro module can fit your needs, is because ryan is used to give more flexibility instead of locking the user to a finished functionality. I mean, there is maybe some hooks that can be used to link things to an user. Without speaking about that you benefit ryan's code and insight ?

Anyway, I will might buy it for a project and could give more feedbacks. Stay tuned.

Yeah, at the end of my reply to yourself I gave ryan an @ but I'll pop him a PM.

Appreciate all the direction on this by the way. It's a fun side project to get more into processwire and luckily the forum is full of great info and direction.

  • Like 1
Link to comment
Share on other sites

To update where I'm at.

So I have - 

  • Added a page ref field to the user template
  • Created a front end button that adds or removes the page id to/from the page ref field.

This means the like module is not needed as I'm not looking to allow anon users to save, only logged in users.

This is the button. I will most likely remove the "remove" button and just have a "saved" item to signify it's saved. I'll allow users to remove in their profile area which outputs their saved posts.

<?php if($user->isLoggedin()): ?>
<form action='<?=$page->url?>' method='post'>
  <?php if($user->bookmarks->has($page)): ?>
  <!-- user already has this page in their bookmarks, show remove button -->
  <button class="button save-button" type='submit' name='bookmark' value='remove'>Remove</button>
  <?php else: ?>
  <!-- show button to add this page to bookmarks -->
  <button class="button save-button" type='submit' name='bookmark' value='add'>Save</button>
  <?php endif; ?>
</form>  
<?php endif; ?>

This is then be actioned by the below. 

$action = $input->post->option('bookmark', [ 'add', 'remove'] );
if($user->isLoggedin() && $action) {
$bookmarks = $user->getUnformatted('bookmarks');
if($action === 'remove') {
// remove bookmark
if($bookmarks->has($page)) $bookmarks->remove($page);
} else if(!$bookmarks->has($page)) {
// add bookmark
$bookmarks->add($page);
}
// save bookmarks
$user->setAndSave('bookmarks', $bookmarks);
// always good to redirect back to self after POST save request
$session->redirect($page->url, false);
}

This works great for the actual post page. So url example being website.com/all/page-name/

However, I have a foreach on website.com/all/ and have the save button on each foreach output.

These in the foreach are called $posts.

I have been able to get it to work by adjusting the above to be $posts instead of $page. However, I find that it just sends the user to the $posts URL but I'd want them to stay on the current page. I did this posts suggestion:

However, I find it doesn't redirect to the /all/ page but goes to /all/page-name/

I also saw a suggested version below to AJAX it which would remove the redirect issue in /all/. So removal of the form element and I'm doing the button show/hide through PHP instead of JS. The snip below doesn't do anything right now and I'm not too sure why.

Any input from someone with JS/AJAX knowledge is appreciated.

<?php if($user->isLoggedin()): ?>
  <?php if($user->bookmarks->has($posts)): ?>
    <!-- user already has this page in their bookmarks, show remove button -->
    <button class="button save-button" name='bookmark' value='remove'>Remove</button>
  <?php else: ?>
    <!-- show button to add this page to bookmarks -->
    <button class="button save-button" name='bookmark' value='add'>Save</button>
  <?php endif; ?>
<?php endif; ?>

<script>
  jQuery(document).ready(function($) {
        // when clicked, send ajax request to server
        $('button.bookmark').on('click', function() {
        var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
        if(btn) $.post('<?= $posts->url ?>', { bookmark: btn.val() })
            .done(function(data) {
            });
        });
    }); 
</script>

Feels good to get this working but just need to hone in the user experience and behaviour. If anyone wants to see the page in question it's here

Liam

Link to comment
Share on other sites

I don't really get what is your issue, the click that still redirect ? If yes, just add `e.preventDefault()` to your js script to prevent default behavior as your are submiting a form.

 

<?php 
/** threat ajax (pw) => https://processwire.com/api/ref/config/#pwapi-methods-runtime
 *  Better to write a dedicated template which will receive the request.
 *  That is a good candidate and exercise to try the module AppApi ?
 */
if($config->ajax && $input->post) {

  bd($input->post->bookmark); // debug with tracy
  /** contain:
   *  action: 'add'
   *  bookmark: '1024'
   */

  /**
   * you can write some logic here, eg., to grab the post data sent by the ajax request
   */
  $bookmarkid = $sanitizer->int($input->post->bookmark);
  // eg. you can get a page from that
  $bookmarkedPage = $pages->get($bookmarkid);
  // send response data back
  $message = 'Borat added bookmark "'. $bookmarkedPage->title .'" ?';
  $success = true;
  
  // build the response data array
  $json = array(
    'message' => $message,
    'success' => $success
  ); 
  // convert data and send as JSON
  header('Content-Type: text/json; charset=utf-8'); // forgot this line
  echo json_encode($json);
  return;
}
?>

<?php // dummy bookmarks loop logic for the show
// foreach($bookmarks as $bookmark) : 
  $bookmark = $pages->get(1024); // forgot this line
?>
<!-- you dont really need a form -->
<!-- <form action='<?=$page->url?>' method='post'> -->
<?php if($user->isLoggedin()): ?>
    <!-- show button to add this page to bookmarks -->
    <!-- add a data attribut to each button -->
    <button class="button bookmark" name='bookmark' value='add' data-id='<?= $bookmark->id ?>'>Save bm #1</button>
<?php endif; ?>
<?php //endforeach; ?>
<!-- </form> -->
<script>
  jQuery(document).ready(function($) {
    // when clicked, send ajax request to server
    $('button.bookmark').on('click', function(e /* add event arg */) {
      var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
      if (!btn) {
        console.warn(`⚠️ Bookmark already saved to profil`);
        return false;
      }
      $.post('<?= $page->url /* send to the page which have the logic and return json answer */ ?>', { action: $(this).val(), bookmark: btn.data('id') })
        .done(function(data) {
          console.log(data, "? response from ajax req");
          // $(this).removeClass('clicked'); // remove `clicked` class, can be added on the foreach loop if the bookmark is already saved and then locked, or whatever..
          console.info(` Bookmark id => ${btn.data('id')} saved to user profil`);
        });        
        
      //e.preventDefault(); // you didn't need a <form>, and if you really want it, uncomment this line to prevent default behavior and stop the redirection
    });        
  }); 
</script>

 

ajx.gif.1b5b743b02c04fda2b1c2b9fefb61b09.gif

 

6 hours ago, Liam88 said:

If anyone wants to see the page in question it's here

giphy.gif?cid=ecf05e47uqm52d4j5pif2nid1o

Link to comment
Share on other sites

10 hours ago, flydev ?? said:

I don't really get what is your issue, the click that still redirect ? If yes, just add `e.preventDefault()` to your js script to prevent default behavior as your are submiting a form.

 

<?php 
/** threat ajax (pw) => https://processwire.com/api/ref/config/#pwapi-methods-runtime
 *  Better to write a dedicated template which will receive the request.
 *  That is a good candidate and exercise to try the module AppApi ?
 */
if($config->ajax && $input->post) {

  bd($input->post->bookmark); // debug with tracy
  /** contain:
   *  action: 'add'
   *  bookmark: '1024'
   */

  /**
   * you can write some logic here, eg., to grab the post data sent by the ajax request
   */
  $bookmarkid = $sanitizer->int($input->post->bookmark);
  // eg. you can get a page from that
  $bookmarkedPage = $pages->get($bookmarkid);
  // send response data back
  $message = 'Borat added bookmark "'. $bookmarkedPage->title .'" ?';
  $success = true;
  
  // build the response data array
  $json = array(
    'message' => $message,
    'success' => $success
  ); 
  // convert data and send as JSON
  header('Content-Type: text/json; charset=utf-8'); // forgot this line
  echo json_encode($json);
  return;
}
?>

<?php // dummy bookmarks loop logic for the show
// foreach($bookmarks as $bookmark) : 
  $bookmark = $pages->get(1024); // forgot this line
?>
<!-- you dont really need a form -->
<!-- <form action='<?=$page->url?>' method='post'> -->
<?php if($user->isLoggedin()): ?>
    <!-- show button to add this page to bookmarks -->
    <!-- add a data attribut to each button -->
    <button class="button bookmark" name='bookmark' value='add' data-id='<?= $bookmark->id ?>'>Save bm #1</button>
<?php endif; ?>
<?php //endforeach; ?>
<!-- </form> -->
<script>
  jQuery(document).ready(function($) {
    // when clicked, send ajax request to server
    $('button.bookmark').on('click', function(e /* add event arg */) {
      var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
      if (!btn) {
        console.warn(`⚠️ Bookmark already saved to profil`);
        return false;
      }
      $.post('<?= $page->url /* send to the page which have the logic and return json answer */ ?>', { action: $(this).val(), bookmark: btn.data('id') })
        .done(function(data) {
          console.log(data, "? response from ajax req");
          // $(this).removeClass('clicked'); // remove `clicked` class, can be added on the foreach loop if the bookmark is already saved and then locked, or whatever..
          console.info(` Bookmark id => ${btn.data('id')} saved to user profil`);
        });        
        
      //e.preventDefault(); // you didn't need a <form>, and if you really want it, uncomment this line to prevent default behavior and stop the redirection
    });        
  }); 
</script>

 

ajx.gif.1b5b743b02c04fda2b1c2b9fefb61b09.gif

 

giphy.gif?cid=ecf05e47uqm52d4j5pif2nid1o

Thanks @flydev ??

 

Tbh I need to spend time to learn more about AJAX and implementation.

I'll try implement this on the single option page so /all/page-name/ and then work on the /all/.

Unless based on the above, I don't need dedicated versions for each page? Again, will have to get digging into AJAX this week.

Cheers

Link to comment
Share on other sites

Yes, you have to make some test on your side to understand better, but that is not very complicated once you get the "flow".

On your example, you can paste my code chunk - if ($config->ajax)... - on your template that refer to the page /all/ and then in the click $.post call, just put the url from $pages->get('/all/')->url, it should work out of the box.

 

PS: If it's a "fun" project, and could be open-source, do not hesitate to export the site-profile to be available to us, it rocks really lol

  • Like 1
Link to comment
Share on other sites

Just an update again.

So I have the AJAX working on the single page. See below woop!

Next steps is trying to get this to work with a variable input. I have taken your jquery snip @flydev ??, again thank you.

Right now I'm just trying to get it to work on this page which has a loop of posts - https://ad-bank.co.uk/all-ads/

This is where I'm at.

The main button section. Only showing a "save" button if the user has not saved the page.

<?php if($user->isLoggedin()): ?>
  <?php if($user->bookmarks->has($posts)): ?>
    	<button class="button bookmark action" type='submit' name='bookmark' disabled>Saved</button>
    <?php else: ?>
    <!-- show button to add this page to bookmarks -->
    	<button class="button bookmark action" type='submit' name='bookmark' value='save' data-id='<?=$posts->url?>'>Save</button>
    <?php endif; ?>
<?php endif; ?>

Next is the action element shown above. This is also within the foreach loop so it is repeated for each button. I found this was the only way I could get it to capture the button action.

Is there a better way to do this without having this action snip repeated for each button? Would it be using the data id attribute which could then sit outside of the loop?

<?php
$action = $input->post->option('bookmark', [ 'save', 'remove'] );
if($user->isLoggedin() && $action) {
$bookmarks = $user->getUnformatted('bookmarks');
// I have now removed "remove" from the button section. Kept it in just in case I do wish to use it.
if($action === 'save' && !$bookmarks->has($posts)) {
// add bookmark
  $bookmarks->add($posts);
}
// save bookmarks
$user->setAndSave('bookmarks', $bookmarks);
$this->halt();
}
?>

Then I have the AJAX request here. This now takes the button data id attribute instead of <?= $page->url ?>. This I found means it grabs the correct url instead of the first url on page that is not saved.

$(document).ready(function($) {
    // when clicked, send ajax request to server
    $('button.bookmark').on('click', function() {
    var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
	// I am passing data id instead of page->url as it will capture the button value needed. Not sure if correct, console shows the correct value.
    if(btn) $.post($(this).data('id'), { action: $(this).val(), bookmark: btn.data('id') })
        .done(function(data) {
        //do something
        console.info(` Bookmark id => ${btn.data('id')} saved to user profile`);
        });
    });
}); 

So in console I get a successful output (see below) but it's not saving into the user. Any thoughts why this might be? I'm thinking it's the action part of this but not sure.

image.thumb.png.236c1e529a6adcbad0afed9e2ba99cf6.png

Overall, feels good to be making progress on this and learning. If anyone has input on the above issues please fire over.

Cheers

Link to comment
Share on other sites

On 9/20/2022 at 10:46 AM, flydev ?? said:

PS: If it's a "fun" project, and could be open-source, do not hesitate to export the site-profile to be available to us, it rocks really lol

Kinda fun so may look too once completed and given a clean up.

Link to comment
Share on other sites

Get rid of writing the $action = $input->post->option('bookmark', [ 'save', 'remove'] );  block in a loop, you do not need that.

If you look at my example (made some corrections for consistency), you are capturing the action made by the user in the `$input->bookmark->action`  and the id in `$input->bookmark->id` (form data built in the $.post() ajax jquery code):

(I don't know what is the `$input->post->option('a', ['b', 'c'])`) thing, but we dont care)

bd($input->post); // debug with tracy
  /** contain:
   *  action: 'save'
   *  bookmark: '1024'
   */

 

Then just handle the request :

<?php 
/** threat ajax (pw) => https://processwire.com/api/ref/config/#pwapi-methods-runtime
 *  Better to write a dedicated template which will receive the request.
 *  That is a good candidate and exercise to try the module AppApi ?
 */
if($config->ajax && $input->post->bookmark) {

  // bd($input->post->bookmark); // debug with tracy, uncomment if you have it
  /** contain:
   *  action: 'save' or 'remove'
   *  bookmark: '1024'
   */
  
  $bookmarkid = $sanitizer->int($input->post->bookmark);
  $action = $sanitizer->name($input->post->action);
  switch($action) { // which action ?
    case 'save': // save logic
      $message = 'Borat saved bookmark "'. $bookmarkid .'" ?';
      $success = true;
      break;
    case 'remove': // remove logic
      $message = 'Borat removed bookmark "'. $bookmarkid .'" ';
      $success = true;
      break;
    default:
      $message = 'error';
      $success = false;
  }
    
  // build the response data array that will be returned in `data` from `.done(function(data) {`
  $json = array( 
    'id' => $bookmarkid,
    'action' => $action,
    'message' => $message,
    'success' => $success
  ); 
  // convert data and send as JSON
  header('Content-Type: text/json; charset=utf-8'); // forgot this line
  echo json_encode($json);
  return;
}
?>


<!-- the only one loop -->

<!-- you dont really need a form -->
<!-- <form action='<?=$page->url?>' method='post'> -->

<?php // dummy bookmarks loop logic for the show
$bookmarks = $pages->find("template=dummypage, limit=5, sort=sort");
foreach ($bookmarks as $bookmark):
?>
<?php if($user->isLoggedin()): ?>
    <!-- show button to save and remove this page to bookmarks -->
	<!-- of course, make your own logic to show one or other button -->
    <!-- add a data attribut to each button -->
    <button class="button bookmark" name='bookmark' value='save' data-id='<?= $bookmark->id ?>'>Save <?= $bookmark->id ?></button>
    <button class="button bookmark" name='bookmark' value='remove' data-id='<?= $bookmark->id ?>'>Remove <?= $bookmark->id ?></button>
<?php endif; ?>
<?php endforeach; ?>

<!-- </form> -->



<script>
  jQuery(document).ready(function($) {
    // when clicked, send ajax request to server
    $('button.bookmark').on('click', function(e /* add event arg */) {
      var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
      if (!btn) {
        console.warn(`⚠️ Bookmark already saved to profil`);
        return false;
      }
      $.post('<?= $page->url /* send to the page which have the logic and return json answer */ ?>', { action: $(this).val(), bookmark: btn.data('id') })
        .done(function(data) {
          console.log(data, "? response from ajax req");
          // $(this).removeClass('clicked'); // remove `clicked` class, can be added on the foreach loop if the bookmark is already saved and then locked, or whatever..
          console.info(` Bookmark id => ${data.id} ${data.action} `);
        });        
        
      //e.preventDefault(); // you didn't need a <form>, and if you really want it, add this line to prevent default behavior and stop the redirection
    });        
  }); 
</script>

 

I will make you an example to save the page, but if I were you, I would just save a "page (ID)" in a page-reference field in the profile.

 

ajx.gif

Edited by flydev ??
illustration
Link to comment
Share on other sites

10 hours ago, flydev ?? said:

Get rid of writing the $action = $input->post->option('bookmark', [ 'save', 'remove'] );  block in a loop, you do not need that.

If you look at my example (made some corrections for consistency), you are capturing the action made by the user in the `$input->bookmark->action`  and the id in `$input->bookmark->id` (form data built in the $.post() ajax jquery code):

(I don't know what is the `$input->post->option('a', ['b', 'c'])`) thing, but we dont care)

bd($input->post); // debug with tracy
  /** contain:
   *  action: 'save'
   *  bookmark: '1024'
   */

 

Then just handle the request :

<?php 
/** threat ajax (pw) => https://processwire.com/api/ref/config/#pwapi-methods-runtime
 *  Better to write a dedicated template which will receive the request.
 *  That is a good candidate and exercise to try the module AppApi ?
 */
if($config->ajax && $input->post->bookmark) {

  // bd($input->post->bookmark); // debug with tracy, uncomment if you have it
  /** contain:
   *  action: 'save' or 'remove'
   *  bookmark: '1024'
   */
  
  $bookmarkid = $sanitizer->int($input->post->bookmark);
  $action = $sanitizer->name($input->post->action);
  switch($action) { // which action ?
    case 'save': // save logic
      $message = 'Borat saved bookmark "'. $bookmarkid .'" ?';
      $success = true;
      break;
    case 'remove': // remove logic
      $message = 'Borat removed bookmark "'. $bookmarkid .'" ';
      $success = true;
      break;
    default:
      $message = 'error';
      $success = false;
  }
    
  // build the response data array that will be returned in `data` from `.done(function(data) {`
  $json = array( 
    'id' => $bookmarkid,
    'action' => $action,
    'message' => $message,
    'success' => $success
  ); 
  // convert data and send as JSON
  header('Content-Type: text/json; charset=utf-8'); // forgot this line
  echo json_encode($json);
  return;
}
?>


<!-- the only one loop -->

<!-- you dont really need a form -->
<!-- <form action='<?=$page->url?>' method='post'> -->

<?php // dummy bookmarks loop logic for the show
$bookmarks = $pages->find("template=dummypage, limit=5, sort=sort");
foreach ($bookmarks as $bookmark):
?>
<?php if($user->isLoggedin()): ?>
    <!-- show button to save and remove this page to bookmarks -->
	<!-- of course, make your own logic to show one or other button -->
    <!-- add a data attribut to each button -->
    <button class="button bookmark" name='bookmark' value='save' data-id='<?= $bookmark->id ?>'>Save <?= $bookmark->id ?></button>
    <button class="button bookmark" name='bookmark' value='remove' data-id='<?= $bookmark->id ?>'>Remove <?= $bookmark->id ?></button>
<?php endif; ?>
<?php endforeach; ?>

<!-- </form> -->



<script>
  jQuery(document).ready(function($) {
    // when clicked, send ajax request to server
    $('button.bookmark').on('click', function(e /* add event arg */) {
      var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
      if (!btn) {
        console.warn(`⚠️ Bookmark already saved to profil`);
        return false;
      }
      $.post('<?= $page->url /* send to the page which have the logic and return json answer */ ?>', { action: $(this).val(), bookmark: btn.data('id') })
        .done(function(data) {
          console.log(data, "? response from ajax req");
          // $(this).removeClass('clicked'); // remove `clicked` class, can be added on the foreach loop if the bookmark is already saved and then locked, or whatever..
          console.info(` Bookmark id => ${data.id} ${data.action} `);
        });        
        
      //e.preventDefault(); // you didn't need a <form>, and if you really want it, add this line to prevent default behavior and stop the redirection
    });        
  }); 
</script>

 

I will make you an example to save the page, but if I were you, I would just save a "page (ID)" in a page-reference field in the profile.

 

ajx.gif

As always, thank you @flydev ??

Straight implementing your snip I'm getting undefined response - 

image.png.6e2cfa36d435cbbad59604840bcf72d4.png

This is the whole template file for /all/ url mentioned above. From what I can see it's implemented right?... So where you mention the "only one loop" is where I have placed the main template build. 

<?php namespace ProcessWire;
$items = $pages->find($selector); //selector built in _func template
$total = $items->getTotal();

if($config->ajax && $input->post->bookmark){

  bd($input->post->bookmark); // debug with tracy, uncomment if you have it
  
  $bookmarkid = $sanitizer->int($input->post->bookmark);
  $action = $sanitizer->name($input->post->action);
  switch($action) { // which action ?
    case 'save': // save logic
      $message = 'Borat saved bookmark "'. $bookmarkid .'" ?';
      $success = true;
      break;
    case 'remove': // remove logic
      $message = 'Borat removed bookmark "'. $bookmarkid .'" ';
      $success = true;
      break;
    default:
      $message = 'error';
      $success = false;
  }
    
  // build the response data array that will be returned in `data` from `.done(function(data) {`
  $json = array( 
    'id' => $bookmarkid,
    'action' => $action,
    'message' => $message,
    'success' => $success
  ); 
  // convert data and send as JSON
  header('Content-Type: text/json; charset=utf-8'); // forgot this line
  echo json_encode($json);
  return;
}
?>

<div id="content-main" class="adbank" pw-append>
  <!-- start of page header -->
<header class="small-header">
	<div class="full-width">
		<div class="row flex header-content">
			<h1><?php echo $page->headline ?></h1>
			<?php 
			if ($page->summary != ''){
				echo''.$page->summary.'';
			}
			?>
		</div>
	</div>
</header>
  <!-- end of page header -->
<div class="bread-plus-filter">
    <div class="full-width">
        <div class="row flex">';
            <!-- Breadcrumbs for all pages except home page -->
            <?php include_once "inc/breadcrumb.php"; ?>
            <div class="filter-surround">
                <div class="filter-row">
                <p><?=$total?> Results</p>
                  <!-- filter button -->
                <button class="button filter-toggle open-menu" type="button" aria-label="Toggle filter">
                    Filter		
                </button>
                </div>
            </div><!-- close of filter surround -->
        </div>
    </div>
</div>

<!-- Main blog post section -->
<section id="main-post-area" class="ab-post-area">
	<div class="full-width">
	<?php if(count($items) > 0){
	}else{
		echo'<h2>Sorry, no results match that filter</h2>';
	}
    ?>
    <div class="row">
    <!-- main creative grid -->
			<div class="creative-posts-container grid">
                <!-- colcade columns -->
                <div class="grid-col grid-col--1"></div>
                <div class="grid-col grid-col--2"></div>
                <div class="grid-col grid-col--3"></div>
				<!-- loop out the posts from the selector -->
                <?php foreach($items as $posts): ?>
                    <div class="creative-post grid-item <?=$posts->ab_channels->title?> <?=$posts->ab_placement->title?> <?=$posts->ab_content->title?> <?=($posts->check_1 ? ' no-title' : '')?> <?=($posts->image_1->height / $posts->image_1->width >= 1.5 ? 'long' : 'short')?> ">
                        <div class="creative-post-options">
                          <!-- posts buttons including view brand, view post and save -->
                            <div class="actions">
                                <a href="<?=$posts->adbank_brands->{httpUrl}?>" class="action" role="link" aria-label="View more of <?=$posts->adbank_brands->{title}?>" title="View more of <?=$posts->adbank_brands->{httpUrl}?>">View Brand</a>
                                <a href="<?=$posts->httpUrl?>" class="action" role="link" aria-label="see more info about this advert" title="see more info about this advert">View Ad</a>
                                <?php if($user->isLoggedin()): ?> <!-- if user is logged in -->
                                    <?php if($user->bookmarks->has($posts)): ?> <!-- if user has saved then show this button -->
                                        <button class="button action" type='submit' name='saved' value='saved' disabled>Saved</button>
                                    <?php else: ?>
                                        <!-- if they haven't saved then show this button to add this page to bookmarks -->
                                        <button class="button bookmark action" type='submit' name='bookmark' value='save' data-id='<?=$posts->id?>'>Save</button> <!-- changed to $posts->id from bookmarksId to match the foreach output -->
                                    <?php endif; ?>
                                <?php else: ?>
                              <!-- if user is not logged in then show this button to fire a login modal -->
                                    <button class="button action save-modal" role="button" title="save this ad bank post" aria-label="save this ad bank post" href="">Save</button>
                                <?php endif; ?>
                                </div>
                            </div>
                      <!-- loop through posts type and include the relevant markup -->
                        <?php
							if($posts->ab_channels->title === "facebook-ads"){
								include "inc/adbank/facebook-ads.php";
							}else if($posts->ab_channels->title === "instagram-ads"){
								include "inc/adbank/instagram-ads.php";
							}else if($posts->ab_channels->title === "pinterest-ads"){
								include "inc/adbank/pinterest-ads.php";
							}else if($posts->ab_channels->title === "youtube-ads"){
								include "inc/adbank/youtube-ads.php";
							}else if($posts->ab_channels->title === "display-ads"){
								include "inc/adbank/display-ads.php";
							}else if($posts->ab_channels->title === "tiktok-ads"){
								include "inc/adbank/tiktok-ads.php";
							}else if($posts->ab_channels->title === "podcast"){
								include "inc/adbank/podcast.php";
							}else{
							}
                        ?>
                    </div>
                <?php endforeach ?>
                
			</div><!-- end of main creative grid -->
		</div><!-- End row -->
	</div><!-- End container -->
</section><!-- End section -->
  
<!-- pagination -->
<?php echo pagination($items); ?>
 <!-- AJAX request -->
<script>
  jQuery(document).ready(function($) {
    // when clicked, send ajax request to server
    $('button.bookmark').on('click', function(e) {
      var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
      if (!btn) {
        console.warn(`⚠️ Bookmark already saved to profile`);
        return false;
      }
      $.post('<?= $page->url?> /*should this be $posts->URL>*/', { action: $(this).val(), bookmark: btn.data('id') })
        .done(function(data) {
          console.log(data, "? response from ajax req");
          console.info(` Bookmark id => ${data.id} ${data.action} `);
        });
    });        
  }); 
</script>
  <!-- end of AJAX request -->
</div> <!-- end of content surround -->

 

Link to comment
Share on other sites

it is not to be excluded that I misunderstood your previous question. I thought you were writing the function `if($config->ajax && $input->post->bookmark) {` in a loop. The template is fine ?

Quote
$.post('<?= $page->url?> /*should this be $posts->URL>*/'  <--- just in case, erase this comment or put the single quote before it :)

The page url should be the page where you write the block which handle your request and return the data: 

<?php namespace ProcessWire;
[...]
if($config->ajax && $input->post->bookmark){
[...]
echo json_encode($json);
return;
[...]

So as the whole code seem to be in the same template, and the template name is `/all/`, then it should be fine.

You can debug and see what/where the request go by looking at your dev. console (in my case, it point to root `/`) :

ajxb8900ebc71bcf867.gif

Link to comment
Share on other sites

Thanks again @flydev ??

Correct, all is within a single template. This is then appended to _main.php. The full template is in my reply above.
To be honest I'm stuck on this so instead of this seeming like a game of circles I'll take some time out to get to grips with it.

From what I can tell, I can not see anything wrong but again that is my lower knowledge barrier.

On button click, I do not get anything firing in console. When I click a second time I then get "⚠️ Bookmark already saved to profile"

A quick question, should any of this part be included and should it be within the loop? 

<?php
if($user->isLoggedin() && $action) {
$bookmarks = $user->getUnformatted('bookmarks');
if($action === 'remove') {
// remove bookmark
if($bookmarks->has($posts)) $bookmarks->remove($posts);
} else if($action === 'save' && !$bookmarks->has($posts)) {
// add bookmark
$bookmarks->add($posts);
}
// save bookmarks
$user->setAndSave('bookmarks', $bookmarks);
$this->halt();
}
?>

Within console -> network I get this when clicking a button and within network -> preview I just get the full page html which I'm guessing shouldn't be the case.

This is the page in question - https://ad-bank.co.uk/all-ads/

 

Anyway, I'll take some time out to get to grips on this but appreciate all the info and direction on this. 

Link to comment
Share on other sites

Quote

On button click, I do not get anything firing in console. When I click a second time I then get "⚠️ Bookmark already saved to profile"

We can see that first, request (XHR) is fired with the right data. You need to show us what is inside the "preview" tab to see what's the answer returned (the payload is what you send, the preview is the answer you get). 

 

About the code in your last post, (I didn't saw it before), should go in inside there:

<?php namespace ProcessWire;
[...]
if($config->ajax && $input->post->bookmark){
[...]
echo json_encode($json);
return;
[...]

This chunk of code is what handle the actions, then put the logic inside it.

if($config->ajax && $input->post->bookmark){

  bd($input->post->bookmark); // debug post with tracy, lets see whats inside

  // if something was submited, but the user is not logged in, nohting will be done and we show an error
  if(!$user->isLoggedin()) {
    $action = ''; // if user not loggedin, let go to default case.
  }
  // user is logged-in
  else {    
    $bookmarkid = $sanitizer->int($input->post->bookmark); // id of the bookmark
    $action = $sanitizer->name($input->post->action); // action that need to be done
    $bookmark = $pages->get($bookmarkid); // instead of $posts, get the bookmark from submitted id to work with
    bd($bookmark, "bm to work with");
    $bookmarks = $user->getUnformatted('bookmarks'); 
    // if($action === 'remove') {
      // save bookmarks
      //$user->setAndSave('bookmarks', $bookmarks);
      //$this->halt();
  }

  switch($action) { // which action ?
    case 'save': // save logic
      if(!$bookmarks->has($bookmark)) {
        // add bookmark
        $bookmarks->add($bookmark);
        $user->setAndSave('bookmarks', $bookmark);
        $message = 'Borat saved bookmark "'. $bookmark->id .'" ?';
      }
      else {
        $message = 'Nothing done';
      }      
      $success = true;
      break;
      
    case 'remove': // remove logic
      if($bookmarks->has($bookmark)) {
        $bookmarks->remove($bookmark);
        $user->setAndSave('bookmarks', $bookmarks);
        $message = 'Borat removed bookmark "'. $bookmark->id .'" ';
      } else {
        $message = 'Nothing done';
      }
      $success = true;
      break;

    default: // Default will throw an error
      $message = 'error borat';
      $success = false;
  }
    
  // build the response data array that will be returned in `data` from `.done(function(data) {`
  $json = array( 
    'id' => $bookmarkid,
    'action' => $action,
    'message' => $message,
    'success' => $success
  ); 
  // convert data and send as JSON
  header('Content-Type: text/json; charset=utf-8'); 
  echo json_encode($json);
  return;
}

 

ajx35e38e30a3a3b840.gif

 

The issue can be that the template is "include" in the _main.php. I look into it later, show us first the request answer. Your are not far from

 

 

Link to comment
Share on other sites

4 minutes ago, flydev ?? said:

You need to show us what is inside the "preview" tab to see what's the answer returned (the payload is what you send, the preview is the answer you get)

The preview just renders the page HTML. It isn't giving a JSON breakdown. 

So you're getting the JSON response, I'm getting a full page render. Once back on later I'll update with a. Screenshot.

Link to comment
Share on other sites

8 minutes ago, flydev ?? said:
case 'save': // save logic
      if(!$bookmarks->has($bookmark)) {
        // add bookmark
        $bookmarks->add($bookmark);
        $user->setAndSave('bookmarks', $bookmark);

AHH awesome, I thought it had to go somewhere. I wasn't sure whether keep in the loop but remove the action line like you mentioned. 

  • Like 1
Link to comment
Share on other sites

1 hour ago, Liam88 said:

The preview just renders the page HTML. It isn't giving a JSON breakdown. 

So you're getting the JSON response, I'm getting a full page render. Once back on later I'll update with a. Screenshot.

That's because header are already sent before, I mean, there is already output.

Move for testing the block, on the top of the _main.php file:

<?php namespace ProcessWire;
[...]
if($config->ajax && $input->post->bookmark){
[...]
echo json_encode($json);
return;
[...]

and adjust : `$.post('<?= $page->url?>` to `$.post('<?= $pages->get('/')->url ?>

Link to comment
Share on other sites

So another update

Script looks like this - 

jQuery(document).ready(function($) {
    // when clicked, send ajax request to server
    $('button.bookmark').on('click', function(e) {
      var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
      if (!btn) {
        console.warn(`⚠️ Bookmark already saved to profile`);
        return false;
      }
      $.post('<?=$pages->get('/')->url?>', { action: $(this).val(), bookmark: btn.data('id') })
        .done(function(data) {
          console.log(data, "? response from ajax req");
          console.info(` Bookmark id => ${data.id} ${data.action} `);
        });
    });        
  }); 

I have put this in _main.php (all template is appended to this via config.) at the top so before anything. I just included it within the namespace opening within the template. I left out the if user is logged in as this feature will only be available for logged in users.

<?php namespace ProcessWire;
if($config->ajax && $input->post->bookmark){

  bd($input->post->bookmark); // debug with tracy, uncomment if you have it
  
  $bookmarkid = $sanitizer->int($input->post->bookmark);
  $action = $sanitizer->name($input->post->action);
  $bookmarks = $user->getUnformatted('bookmarks');
  switch($action) {
    case 'save': // save logic
      if(!$bookmarks->has($bookmark)) {
        // add bookmark
        $bookmarks->add($bookmark);
        $user->setAndSave('bookmarks', $bookmark);
        $message = 'Borat saved bookmark "'. $bookmark->id .'" ?';
      }
      else {
        $message = 'Nothing done';
      }      
      $success = true;
      break;
    case 'remove': // remove logic
      if($bookmarks->has($bookmark)) {
        $bookmarks->remove($bookmark);
        $user->setAndSave('bookmarks', $bookmarks);
        $message = 'Borat removed bookmark "'. $bookmark->id .'" ';
      } else {
        $message = 'Nothing done';
      }
      $success = true;
      break;
    default:
      $message = 'error';
      $success = false;
  }
    
  // build the response data array that will be returned in `data` from `.done(function(data) {`
  $json = array( 
    'id' => $bookmarkid,
    'action' => $action,
    'message' => $message,
    'success' => $success
  ); 
  // convert data and send as JSON
  header('Content-Type: text/json; charset=utf-8'); // forgot this line
  echo json_encode($json);
  return;
}
?>

So now I get this within preview (video below). Still full html render output (Any thoughts why would this be?) but now the JSON is appended. I have also tried placing the if AJAX within the _init.php file. This just prepends it (expected) to before the HTML output. So I suppose my issue is I'm rendering the full HTML output as the JSON just shows as part of it.

 

 

Link to comment
Share on other sites

28 minutes ago, Liam88 said:

Any thoughts why would this be?

Yes, because of the rendering order - (I am used to use the Delayed Output Strat so I can't tell you right now what's going on for real) - wrap the HTML output with `if(!$config->ajax)`. example :

_main.php:

<?php namespace ProcessWire;
// place this on the top - easier for us to debug for your project.
// but with the good logic, you could do it by feeling.
// 
// return content set in `header()`  (ext/json here)
if($config->ajax && $input->post->bookmark) {

  bd($input->post->bookmark); // debug with tracy, uncomment if you have it
  
  $bookmarkid = $sanitizer->int($input->post->bookmark);
  $action = $sanitizer->name($input->post->action);
  $bookmarks = $user->getUnformatted('bookmarks');
  switch($action) {
    case 'save': // save logic
      if(!$bookmarks->has($bookmark)) {
        // add bookmark
        $bookmarks->add($bookmark);
        $user->setAndSave('bookmarks', $bookmark);
        $message = 'Borat saved bookmark "'. $bookmark->id .'" ?';
      }
      else {
        $message = 'Nothing done';
      }      
      $success = true;
      break;
    case 'remove': // remove logic
      if($bookmarks->has($bookmark)) {
        $bookmarks->remove($bookmark);
        $user->setAndSave('bookmarks', $bookmarks);
        $message = 'Borat removed bookmark "'. $bookmark->id .'" ';
      } else {
        $message = 'Nothing done';
      }
      $success = true;
      break;
    default:
      $message = 'error';
      $success = false;
  }
    
  // build the response data array that will be returned in `data` from `.done(function(data) {`
  $json = array( 
    'id' => $bookmarkid,
    'action' => $action,
    'message' => $message,
    'success' => $success
  ); 
  // convert data and send as JSON
  header('Content-Type: text/json; charset=utf-8'); // forgot this line
  echo json_encode($json);
  return;
}
?>



<?php
// HTML code need to be rendered when a normal request happen (non-ajax, the user dont want the JSON thing, he want text/html to see it in his browser page)
  
// render HTML only if not ajax detected
if(!$config->ajax): ?>

<!DOCTYPE html>
<html lang="en-gb">
	<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <link rel="alternate" href="https://ad-bank.co.uk/" hreflang="en-gb" />
    <title>
    Browse Over 1000 Ad Creative Examples | Adbank.co.uk    </title>
    <!-- .... -->
  </head>

<body class="body">
  
	<!-- Page Header -->
	<!-- Navigation -->
    <nav id="nav" class="unhidden">
        <!-- .... -->
    </nav>
  <div id="content-main" class="adbank">
	
    <!-- .... -->
  
    <header class="small-header">
        <div class="full-width">
            <div class="row flex header-content">
                <h1>This is everything!</h1>
                <p>Here are all the ads we have collated. It seems a lot, it is and it's only growing. Check out the filters or the brand and industry sections. Struggling for inspiration? The blog has features collections based on styles, industries and our personal favourites.</p>		
          	</div>
        </div>
    </header>
  
    <!-- .... -->
  
	<div class="breadcrumbs">
        <!-- .... -->
	</div>

    <!-- .... -->

    <section id="main-post-area" class="ab-post-area">
      <div class="full-width">
        <div class="row">
              ...... 
        </div>
      </div>
    </section>

    <!-- .... -->
  </div>	
  	
  <footer> great success </footer>

  
  <!-- PUT OUR JQUERY SCRIPT HERE AT THE VERY BOTTOM -->
  <script>
  jQuery(document).ready(function($) {
    // when clicked, send ajax request to server
    $('button.bookmark').on('click', function(e) {
      var btn = $(this).hasClass('clicked') ? false : $(this).addClass('clicked');
      if (!btn) {
        console.warn(`⚠️ Bookmark already saved to profile`);
        return false;
      }
      $.post('<?=$pages->get('/')->url?>', { action: $(this).val(), bookmark: btn.data('id') })
        .done(function(data) {
          console.log(data, "? response from ajax req");
          console.info(` Bookmark id => ${data.id} ${data.action} `);
        });
    });        
  }); 
  </script>
  
</body>
</html>
<?php endif; // /render HTML ?>

 

Adjust you code, fire the button, report back    ()

 

 

 

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.
×
×
  • Create New...