Search the Community

Showing results for tags 'ajax'.



More search options

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Welcome to ProcessWire
    • News & Announcements
    • Showcase
    • Wishlist & Roadmap
  • Community Support
    • Getting Started
    • Tutorials
    • FAQs
    • General Support
    • API & Templates
    • Modules/Plugins
    • Themes and Profiles
    • Multi-Language Support
    • Security
    • Jobs
  • Off Topic
    • Pub
    • Dev Talk

Categories

There are no results to display.


Found 80 results

  1. I just relaunched my portfolio website. It's my first ajax driven website using ProcessWire as a CMS. Its a showcase of some of my work as well as a digital playground to improve my coding skills. If you encounter any bugs or have feedback, feel free to share janploch.de
  2. Not really sure if this is the best place to post this (or if it belongs in the thread for the module itself, but if it needs to be moved please do so. I successfully got graphql set up and love how easy it makes it to query etc. In my endpoint, I have: echo $modules->get('ProcessGraphQL')->executeGraphQL(); And on another page, I was testing with: $.ajax({ type: "POST", url: 'localhost/pw/graphql/', data: "{ modals(s: \"title=Test-Page\") { list { id title body } } }", success: function(data) { console.log(data); } }); However, I seem to be getting an error message returned with: "Must provide an operation.". I do apologize if I have just missed something basic (very new to ajax and how it all functions), but is there something missing from my initial request?
  3. I'm trying to make an AJAX call from within a template to a php script within my templates folder, but I'm getting a 404 from all URLs. Is there a proper way to directly address scripts within PW templates? I've read it will work in the site root, but I'd rather keep all the code together if possible.
  4. Hello, After doing a deep dive into Processwire over the weekend (better late than never), I'm starting to layout an app to receive a migration of an existing app. I'm sure I'll have several questions, but here's my first: Question: I'm using the _main.php/regions template approach. However, I need to do some ajax calls for some html. How would I tell a page just to include only the needed template/fields? I can probably figure this out, but in the interest of time...Thanks!
  5. Hi there, I ran into a problem posting form data via AJAX. Of course I did some research on the topic via Google, this forum and looking into ProcessWire's core code and found the following steps... Setting headers to 'X-Requested-With', 'XMLHttpRequest' Using a trailing slash ...but to no avail. As you can see in the following code I log the response, in this case just a var_dump() of wire("input")->post, but i just get response: object(ProcessWire\WireInputData)#240 (0) { } Here's my code in total. It's simplified, but should work, but for some reason does not. Could any of you point me towards the solution/my fallcy? Thanks in advance! <?php if (wire("config")->ajax) { if (wire("input")->post) { var_dump(wire("input")->post); } } else { ?> <div id="app"> <form action="./" method="post" v-on:submit.prevent="getFormValues"> <label><input type="radio" name="yes_no" id="yes" <?= ($page->yes_no == "1") ? "checked" : "" ?> value="1"/>Ja</label> <label><input type="radio" name="yes_no" id="no" <?= ($page->yes_no == "2") ? "checked" : "" ?> value="2"/>Nein</label> <button type="submit">Go</button> <p>{{ yes_no }}</p> </form> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> new Vue({ el: '#app', data: { yes_no: '(unbeantwortet)' }, methods: { getFormValues: function (e) { this.yes_no = e.target.elements.yes_no.value; axios.post('./', { yes_no: this.yes_no }, { headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' } }) .then(function (response) { console.log("response:", response.data); }) .catch(function (error) { console.log("error:", error); }); } } }); </script> <?php } ?> // edit: I forgot to mention that the POST request is sent (I can track it in developer tools) and it includes the data I'm intending to send. So I really assume it is a reception problem.
  6. Hi there, I'm trying to get to work some AJAX call with vanilla Javascript, not jQuery. Anything seems to work so far, but the !$config->ajax seems to be ignored. To find out whats the problem by comparing both, jquery and plain javascipt, i built this template. commenting out //loadJquery(''); or loadVanilla(''); switches the two variants. (empty url variable means that the same pages will be loaded.) The problem: the pure Javascript function ("loadVanilla") is loading the full page content into the dc-container, which is wrong. The jQuery function ("loadJquery") only loads the part outside of the if(!$config->ajax): - which is as it should be. So - any help with this, what am i doing wrong? Thanks a lot - Matze <?php namespace ProcessWire; if(!$config->ajax): ?> (some static content)<br> <a id="loadlink" href="#">load</a><br> <?php endif; // end if not ajax ?> <span id="dc-container">(dynamic content)</span> <?php if(!$config->ajax): ?> <script src="http://code.jquery.com/jquery-3.3.1.min.js"></script> <script> var loadlink = document.getElementById('loadlink'); loadlink.addEventListener('click', function(event) { //loadJquery(''); loadVanilla(''); event.preventDefault() }); function loadVanilla(url) { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("dc-container").innerHTML = 'loaded: ' + this.responseText + (' (by vanilla javascript)'); } }; xhttp.open("POST", "", true); xhttp.send(); } function loadJquery(url){ // Load content $.ajax({ type: "POST", url: url, data: { ajax: true }, success: function(data,status){ pageData = data; } }).done(function(){ // when finished and successful document.getElementById("dc-container").innerHTML = 'loaded: ' + pageData + ' (by jquery ajax)'; }); } </script> <?php endif; // end if not ajax ?>
  7. Hi folks! For a website Iam working on I need to (pre)load a huge amount of images (100-500) from a folder in assets (wich I upload via FTP). To preload them I want to add them to the DOM inside a container, that I hide with css. This images will be use for a frame by frame animation (that animates with scrolling) so they should be loaded parallel and if the user clicks a cancel button, the loading should be canceled. (My website is using ajax to load pages with different animations, and the loading of the second animation waits till the loading of the first animation is loaded completly, wich I want to prevent). I want to use ajax to do this, so I can cancel the loading with xhr.abort(); Here is my code: var folder = '{$config->urls->assets}sequenzen/test/'; xhr = $.ajax({ url : folder, success: function (data) { $(data).find("a").attr("href", function (i, val) { if( val.match(/\.(jpe?g|png|gif)$/) ) { $(".preloader").append( "<img src='"+ folder + val +"'>" ); } }); } }); this will give me a 403 forbidden error. After some research I found out that I have to put a .htaccess in my assets folder. I also tried putting it in the sub folder "test", where the files are, but Iam still getting the error. Is there anything else Iam missing? Is there a configuration in PW i have to change to do that?
  8. Has anyone used pagination with ajax driven content? I'm using the MarkupPagerNav module to give me pagination, but my content is updated by an ajax post to refresh the body content of a table. The pagination links show a myproject/http404/?page=n for each link generated so they give a 404 page not found. I believe this is because the file containing my ajax code resides in the root of my project as a "web service" and does not have the access it needs, but I'm unsure really. If anyone can throw any light on this I would be very grateful. I include the code with my ajax below. <?php namespace ProcessWire; require_once ('./index.php'); // bootstrap ProcessWire if($config->ajax) { $pg = $pages->get("/single-use-carrier-bags/"); $selector = wire('input')->post('selector'); $mytableContent = ''; // hold the table markup // $retailer = $pg->children($selector); // for info. only $selector looks like this -> $retailer = $pg->children("sort=title, include=all, limit=10"); $pageinate = $retailer->renderPager(); echo $pageinate; foreach ($retailer as $r) { $mytableContent .= "<tr> <td><a href= '$r->url' target='_blank'>$r->title</a></td> <td>$r->category</td> <td>".numFormat($r->no_bags_issued)."</td> <td>".numFormat($r->gross_proceeds)."</td> <td>".numFormat($r->net_proceeds)."</td> <td>$r->other_use_net_proceeds</td> <td>".numFormat($r->amount_donated)."</td> <td>$r->good_causes_in_receipt</td> <td>".numFormat($r->no_paper_bags_issued)."</td> <td>".numFormat($r->no_bags_for_life)."</td> </tr>"; } echo $mytableContent; } ?>
  9. I have a table in my template that I would like to update using Ajax when a button is pressed. I am not sure how to go about this and hope somebody here could point me in the right direction. Here is my code for the table output. You can see I loop through the fields to output using a hard wired selector to list the retailer type. I have a button for Supermarkets that fires a js handler myFunc and I need to put the ajax there to call this page again with a param so I can then form the selector for supermarkets and update the table. When I get this working and understand how to do it in ajax I will code up a selector that is formed dependant on a bank of buttons and the table will update without the rest of the page - like magic! That's what ajax is for right?! Thanks for any help - Paul <button type="button" onclick="myFunc()" value="1" class="btn btn-primary">Supermarkets</button> <div class= "table-responsive"> <table class="table table-hover table-striped"> <thead> <tr> <th>Retailer</th> <th>No. bags issued</th> <th>Gross proceeds £</th> <th>Net proceeds £</th> <th>Other use of net proceeds</th> <th>Amount donated £</th> <th>Good causes in receipt of proceed</th> <th>No. of paper bags issued</th> <th>No. of bags for life</th> </tr> </thead> <tbody> <?php $pg = $pages->get("/single-use-carrier-bags/"); $retailer = $pg->find("category=DIY, sort=title, include=all"); foreach ($retailer as $r) { echo "<tr> <td><a href= '$r->url'>$r->title</a></td> <td>".numFormat($r->no_bags_issued)."</td> <td>".numFormat($r->gross_proceeds)."</td> <td>".numFormat($r->net_proceeds)."</td> <td>$r->other_use_net_proceeds</td> <td>".numFormat($r->amount_donated)."</td> <td>$r->good_causes_in_receipt</td> <td>".numFormat($r->no_paper_bags_issued)."</td> <td>".numFormat($r->no_bags_for_life)."</td> </tr>"; } ?> </tbody> </table> </div> </div> </div> <script type="text/javascript"> function myFunc() { } </script>
  10. hello, i have a _func.php file with this function. in the documentation i read that some api variables in functions are not available, so i use wire('pages'). function doSomething($u, $p) { $p = wire('pages')->get("id=$p"); $u = wire('pages')->get("id=$u"); $p->of(false); { ... populate repeater field stuff } $p->save(); } if i call this function in my home.php like doSomething(41,1093) (only for testing!) everything is fine, the function works, it add items to a repeater field. the german says "Wenn es dem Esel zu wohl ist, geht er aufs Eis", so i play around with ajax to fire up this function. $(document).ready(function() { $("#hit").click(function(){ $.ajax({ url: '<?= $config->urls->templates?>includes/_func.php/', type: 'post', data: {userID: "<?= $user->id ?>", pageID: "<?= $page->id ?>"}, success: function(output){ console.log(output); } }) }) }) i read something about variable scopes and i think i understand it a little bit. but i don't understand why doSomething(41,1093) works in home.php the ajax call runs into a Call to undefined function wire() ? also i tried if ($config->ajax) but no luck ... that's the relevant party of _func.php. function doSomething($u, $p) { $p = wire('pages')->get("id=$p"); $u = wire('pages')->get("id=$u"); $p->of(false); { ... populate repeater field stuff } $p->save(); } if(isset($_POST['userID']) && !empty($_POST['userID']) && isset($_POST['pageID']) && !empty($_POST['pageID'])) { $u = $_POST['userID']; $p = $_POST['pageID']; { ... } echo sendLike($u, $p); } where is my mistake? any ideas? thx
  11. Hello, I'm desperately trying to update my website to PW 3.0.62 and I'm facing issues to to module compatibility. I was stuck with Pages2Pdf which I managed to solve bu updating the module from Github, but now it's the Pages Web Service module... and this time, I don't know wht to do The Module is found there. But it is quite old and I can't find it in the modules catalogue... and my site is making quite a use of it (I can't think of a way to do otherwise, sorry...) After adding the FileCompiler=0 to the module pages, the error I'm stuck with is : Fatal error: Class 'WireData' not found in /home/celfred/PlanetAlert/site/modules/ServicePages/ServicePages.module on line 22 and I have no idea on what to do... I must admit I'm not a programmer but a middle-school teacher... (for your information, here's the site I'm talking about : http://planetalert.tuxfamily.org ) but I'm struggling hard to solve the different issues I have to face and I'm wlling to understand things. I have just spent many hours trying to make 2.8 work on my localhost (and it seems ok ) but I'd like to switch to 3.x to prepare the future If anyone had the will to spend a few minutes to try and help me, I would greatly appreciate. Thanks in advance ! If you need more information to understand my problem, feel free to ask.
  12. I want to implement tables with Dynatable populated via ajax. Here's what I could muster: if($config->ajax) { $results = $pages->find($sanitizer->selector()); foreach($results as $r) $array[] = [ $r->title, $r->modified ]; header("Content-type: application/json"); echo json_encode($array); exit; } $content .= <<<EOT <table id="my-ajax-table"></table> <script> $("#my-ajax-table").dynatable({ dataset: { ajax: true, ajaxUrl: '.', ajaxOnLoad: true, records: [] } }); </script> EOT; But that's really nothing. Pro tips?
  13. Hi I'm using a lightly modified Version of ProcessSlider on my PW page (3.0.42). But I'm having a problem with the file upload. It's using Ajax File upload to upload images, which is working fine at first, but the images are only uploaded temporarily (creation date 01.01.1970) when an image is uploaded and saved. When pressing the "save"-Button, this does not change. I'm guessing it's due to changes from PW 2 to 3? This is the js that is calling the iframe upload, I think I don't know what to do to amke it save correctly. Anybody who can give me a hint? /** * ProcessWire iFrameImagePicker plugin * * Light verision of InputfieldCKEditor/plugins/pwimage/plugin.js * * @return callback(src, width, height) * */ function loadIframeImagePicker(page_id, callback) { var page_id = page_id;//$("#Inputfield_id").val(); var edit_page_id = page_id; var file = ''; var imgWidth = 0; var imgHeight = 0; var imgDescription = ''; var imgLink = ''; var hidpi = false; var modalUri = config.urls.admin + 'page/image/'; var queryString = '?id=' + page_id + '&edit_page_id=' + edit_page_id + '&modal=1'; if(file.length) queryString += "&file=" + file; if(imgWidth) queryString += "&width=" + imgWidth; if(imgHeight) queryString += "&height=" + imgHeight; queryString += '&hidpi=' + (hidpi ? '1' : '0'); if(imgDescription && imgDescription.length) { queryString += "&description=" + encodeURIComponent(imgDescription); } if(imgLink && imgLink.length) queryString += "&link=" + encodeURIComponent(imgLink); queryString += ("&winwidth=" + ($(window).width() - 30)); // create iframe dialog box var modalSettings = { title: "<i class='fa fa-fw fa-folder-open'></i> " + "Select Image", open: function() { } }; var $iframe = pwModalWindow(modalUri + queryString, modalSettings, 'large'); $iframe.load(function() { // when iframe loads, pull the contents into $i var $i = $iframe.contents(); if($i.find("#selected_image").size() > 0) { // if there is a #selected_image element on the page... var buttons = [ { html: "<i class='fa fa-camera'></i> " + "Insert This Image", click: function() { var $i = $iframe.contents(); var $img = $("#selected_image", $i); $iframe.dialog("disable"); $iframe.setTitle("<i class='fa fa-fw fa-spin fa-spinner'></i> " + "Saving Image"); $img.removeClass("resized"); var width = $img.attr('width'); if(!width) width = $img.width(); var height = $img.attr('height'); if(!height) height = $img.height(); var file = $img.attr('src'); var page_id = $("#page_id", $i).val(); var hidpi = $("#selected_image_hidpi", $i).is(":checked") ? 1 : 0; var rotate = parseInt($("#selected_image_rotate", $i).val()); file = file.substring(file.lastIndexOf('/')+1); var resizeURL = modalUri + 'resize?id=' + page_id + '&file=' + file + '&width=' + width + '&height=' + height + '&hidpi=' + hidpi; if(rotate) resizeURL += '&rotate=' + rotate; if($img.hasClass('flip_horizontal')) resizeURL += '&flip=h'; else if($img.hasClass('flip_vertical')) resizeURL += '&flip=v'; $.get(resizeURL, function(data) { var $div = $("<div></div>").html(data); var src = $div.find('#selected_image').attr('src'); callback(src, width, height); $iframe.dialog("close"); }); } }, { html: "<i class='fa fa-folder-open'></i> " + "Select Another Image", 'class': 'ui-priority-secondary', click: function() { var $i = $iframe.contents(); var page_id = $("#page_id", $i).val(); $iframe.attr('src', modalUri + '?id=' + page_id + '&modal=1'); $iframe.setButtons({}); } }, { html: "<i class='fa fa-times-circle'></i> " + "Cancel", 'class': 'ui-priority-secondary', click: function() { $iframe.dialog("close"); } } ]; $iframe.setButtons(buttons); $iframe.setTitle("<i class='fa fa-fw fa-picture-o'></i> " + $i.find('title').html()); } else { var buttons = []; $("button.pw-modal-button, button[type=submit]:visible", $i).each(function() { var $button = $(this); var button = { html: $button.html(), click: function() { $button.click(); } } buttons.push(button); if(!$button.hasClass('pw-modal-button-visible')) $button.hide(); }); var cancelButton = { html: "<i class='fa fa-times-circle'></i> " + "Cancel", 'class': "ui-priority-secondary", click: function() { $iframe.dialog("close"); } }; buttons.push(cancelButton); $iframe.setButtons(buttons); } }); }
  14. I'm in the process of rebuilding a WordPress site over into ProcessWire (Yay!) I'm using Ajax to call a page (ajaxAgenda) from the homepage which includes a PHP file (partialAgenda) which makes the call. This is what I got in the included partial. Notice that the URL has a / at the end to prevent redirection. var page_number = 1; var get_agenda = function(){ $.ajax({ type : "GET", data : {pnum: page_number}, dataType : "html", url : "/components/ajaxAgenda/", beforeSend : function(){ $('#ajax-loader2').show(); }, success : function(data){ $('#ajax-loader2').hide(); $('#agenda').html(data); } }); } And in the Ajax called file I have the following echo $page->url.'<br>'; echo $input->get->pnum.'<br>'; echo $_GET['pnum'].'<br>'; which only outputs "/components/ajaxagenda/". pnum is empty and get is also also empty It seems like the variables are simply being stripped from my Ajax call. Or am I missing something really obvious?
  15. Hi everyone I've started to build a newsfeed for a customer, but I'm stuck... I have a news-repeater field including date, title, text and images. I have the link (showing date and title) to each news on the left (with foreach) and the details on the right (showing date, title, text and images also with foreach). I would love to only show the current news on the right. The latest news should be visible on the right. When I click on the link on the left side, it should change the news content on the right side. Does anyone have a simple solution for this? Here's how it looks like at the moment: http://rolspace.net/hd/energiegossau/uber-uns/news/ Thank you! Roli
  16. Hi folks, On the website I am working on I do have a form which needs to be filled in (duh) and when the user clicks on download, the data is send with ajax and processed in the same file by using the ProcessWire API with $config->ajax; The data which is provided by the user will be checked with data in my database. If the data is correct I want to push a file download. But this is not working at the moment. It seems like the content of the (.exe) file is pasted in the console instead of pushing the file. My code looks like this: if ($config->ajax){ $serial = $input->post('serial'); $DB = DB(); $query = $DB->prepare("/*My query here*/"); $query->bindParam("comparision", $serial, PDO::PARAM_STR); $query->execute(); $reply = ''; $SN_rows = $query->rowCount(); if (!$SN_rows > 0) { $reply = 'error'; } else { $reply = 'success'; $installer = $page->attachments->eq(1); $filename = "Filename"; $filepath = $installer->filename; $options = array( 'forceDownload' => true, 'exit' => true, 'downloadFilename' => $filename ); wireSendFile($filepath, $options); } echo $reply; exit(); } What am I doing wrong here or is it a bug? Is there another way to do this with the processwire API? Thanks in advance, ~Harmen
  17. After this tutorial you'll have learned how to: Build a Process module Make an AJAX request to backend Serve JSON as response Let's say you want to display the latest orders in a dashboard that you can access from admin panel. And you want it to refresh its content with a button click. Most straightforward and proper way (that I know of) is to create a Process module, as they're built for this purpose. First, create a directory under /site/modules/, call it ProcessDashboard, and create a file named ProcessDashboard.module under that directory. Following is about the least amount of code you need to create a Process module. <?php namespace ProcessWire; class ProcessDashboard extends Process { public static function getModuleInfo() { return [ 'title' => 'Orders Dashboard', 'summary' => 'Shows latest orders', 'version' => '0.0.1', 'author' => 'abdus', 'autoload' => true, // to automatically create process page 'page' => [ 'name' => 'order-dashboard', 'title' => 'Orders', 'template' => 'admin' ] ]; } public function ___execute() { return 'hello'; } } Once you refresh module cache from Modules > Refresh, you'll see your module. Install it. It will create an admin page under admin (/processwire/) and will show up as a new item in top menu, and when you click on it, it will show the markup we've built in execute() function. All right, now let's make it do something useful. Let's add create a data list to display latest orders. We'll change execute() function to render a data table. public function ___execute() { /* @var $table MarkupAdminDataTable */ $table = $this->modules->MarkupAdminDataTable; $table->setID($this->className . 'Table'); // "#ProcessDashboardTable" $table->headerRow([ 'Product', 'Date', 'Total' ]); // fill the table foreach ($this->getLatest(10) as $order) { $table->row([ $order['title'], $order['date'], $order['total'] ]); } // to refresh items $refreshButton = $this->modules->InputfieldSubmit; $refreshButton->name = 'refresh'; $refreshButton->id = $this->className . 'Refresh'; // "#ProcessDashboardRefresh" $refreshButton->value = 'Refresh'; // label of the button return $table->render() . $refreshButton->render(); } where getLatest() function finds and returns the latest orders (with only title, date and total fields) protected function getLatest($limit = 5, $start = 0) { // find last $limit orders, starting from $start $orders = $this->pages->find("template=order, sort=-created, limit=$limit, start=$start"); // Only return what's necessary return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total ]; }); } When you refresh the page, you should see a table like this Now we'll make that Refresh button work. When the button is clicked, it will make an AJAX request to ./latest endpoint, which will return a JSON of latest orders. We need some JS to make AJAX request and render new values. Create a JS file ./assets/dashboard.js inside the module directory. window.addEventListener('DOMContentLoaded', function () { let refresh = document.querySelector('#ProcessDashboardRefresh'); let table = document.querySelector('#ProcessDashboardTable'); refresh.addEventListener('click', function (e) { // https://developer.mozilla.org/en/docs/Web/API/Event/preventDefault e.preventDefault(); // Send a GET request to ./latest // http://api.jquery.com/jquery.getjson/ $.getJSON('./latest', { limit: 10 }, function (data) { // check if data is how we want it // if (data.length) {} etc // it's good to go, update the table updateTable(data); }); }); function renderRow(row) { return `<tr> <td>${row.title}</td> <td>${row.date}</td> <td>${row.total}</td> </tr>`; } function updateTable(rows) { table.tBodies[0].innerHTML = rows.map(renderRow).join(''); } }); And we'll add this to list of JS that runs on backend inside init() function public function init() { $scriptUrl = $this->urls->$this . 'assets/dashboard.js'; $this->config->scripts->add($scriptUrl); } Requests to ./latest will be handled by ___executeLatest() function inside the module, just creating the function is enough, PW will do the routing. Here you should notice how we're getting query parameters that are sent with the request. // handles ./latest endpoint public function ___executeLatest() { // get limit from request, if not provided, default to 10 $limit = $this->sanitizer->int($this->input->get->limit) ?? 10; return json_encode($this->getRandom($limit)); } Here getRandom() returns random orders to make it look like there's new orders coming in. protected function getRandom($limit = 5) { $orders = $this->pages->find("template=order, sort=random, limit=$limit"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total ]; }); } And we're done. When refresh button is clicked, the table is refreshed with new data. Here it is in action: 2017-04-29_19-01-40.mp4 (227KB MP4, 0m4sec) Here's the source code: https://gist.github.com/abdusco/2bb649cd2fc181734a132b0e660f64a2 [Enhancement] Converting page titles to edit links If we checkout the source of MarkupAdminDataTable module, we can see we actually have several options on how columns are built. /** * Add a row to the table * * @param array $a Array of columns that will each be a `<td>`, where each element may be one of the following: * - `string`: converts to `<td>string</td>` * - `array('label' => 'url')`: converts to `<td><a href='url'>label</a></td>` * - `array('label', 'class')`: converts to `<td class='class'>label</td>` * @param array $options Optionally specify any one of the following: * - separator (bool): specify true to show a stronger visual separator above the column * - class (string): specify one or more class names to apply to the `<tr>` * - attrs (array): array of attr => value for attributes to add to the `<tr>` * @return $this * */ public function row(array $a, array $options = array()) {} This means, we can convert a column to link or add CSS classes to it. // (ProcessDashboard.module, inside ___execute() method) // fill the table foreach ($this->getLatest(10) as $order) { $table->row([ $order['title'] => $order['editUrl'], // associative -> becomes link $order['date'], // simple -> becomes text [$order['total'], 'some-class'] // array -> class is added ]); } Now, we need to get page edit urls. By changing getLatest() and getRandom() methods to return edit links in addition to previous fields protected function getLatest($limit = 5, $start = 0) { // find last $limit orders, starting from $offset $orders = $this->pages->find("template=order, sort=-created, limit=$limit, start=$start"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total, 'editUrl' => $order->editUrl ]; }); } protected function getRandom($limit = 5) { $orders = $this->pages->find("template=order, sort=random, limit=$limit"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total, 'editUrl' => $order->editUrl ]; }); } and tweaking JS file to render first column as links function renderRow(row) { return `<tr> <td><a href="${row.editUrl}">${row.title}</a></td> <td>${row.date}</td> <td>${row.total}</td> </tr>`; } we get a much more practical dashboard.
  18. I have been wanting to set up a quick "dashboard" for a recent project, and was considering using ajax to get a "real time" update/refresh on the page (some of my output depends on the datetime field). I know how to get the desired output using php with some if statements comparing todays date to the date stored in the field, however, I am a bit at a lose of how to interact with the ProcessWire API using ajax to get the desired effect. I found the following and know that I need to start off with: <?php if($config->ajax) { // page was requested from ajax } Unfortunately, as I mentioned earlier, how would one actual find pages using a template and get the field contents from them using AJAX? I apologize if this seems bit broad, but I am stil getting a grasp of using AJAX to deliver content to get a real time update on a page without refresh.
  19. Hi, I am building a site containing around 50000 products which will be updated on a regular basis. We are far from putting this site into production. I am actually devising the future functionalities. The actual update script uses an AJAX process so to avoid timeout. However, the update process will have to be fired automatically, something with either curl or phantom.js. What I have coded at the moment isn't probably not the best solution, albeit it works pretty well. There is a PW template called from an address : http://example.com/start-ajax which starts on load a simple jQuery Ajax process. The 50000 products are listed in a CSV file that is previoulsy broken down into small pieces (200 lines per file). The process is smooth and takes 40 minutes to read the data and update/or create Processwire pages accordingly. I would like to know your advices, your experience in that matter. What would you do to create an automated batch file?
  20. I'm trying to call an ajax function for an inputfield using Soma's method below by intercepting the call in the module init function but for some reason $this->input is always NULL. public function init() { parent::init(); $dir = dirname(__FILE__); require_once("$dir/Location.php"); require_once("$dir/LocationArray.php"); $this->wire('config')->js('InputfieldLocations', array( 'url' => "/admin/locationmodule/ajax" )); if ($this->config->ajax && $this->input->ajaxfunction == 'getcities') { header('Content-Type: application/json'); echo $this->get_cities(); exit; } }
  21. Hey, I've been trying to implement some progressive enhancements to take advantage of modern browsers, modern JS and CSS capabilities etc. One thing I've got stuck is to CSRF protection using $session->CSRF. I'm echoing hidden token using <?= session()->CSRF->renderInput('contact') ?> inside the form, and I can validate the token with plain POST request (without AJAX) just fine using session()->CSRF->hasValidToken('contact'). For AJAX, I'm setting headers x-<tokenname>:<tokenvalue>. It shows up in both Chrome DevTools correctly, and on backend lke HTTP_X_<TOKENNAME> => <TOKEN_VALUE> as expected, so there's no miscommunication. But, when I try to validate it, it fails no matter what. After some debugging I realized at each request, a new token is created, which invalidates the one I'm sending. Here are the relevant parts from the core. Inside /wire/core/SessionCSRF.php, where validation occurs <?php public function hasValidToken($id = '') { $tokenName = $this->getTokenName($id); $tokenValue = $this->getTokenValue($id); // ... if($this->config->ajax && isset($_SERVER["HTTP_X_$tokenName"]) && $_SERVER["HTTP_X_$tokenName"] === $tokenValue) return true; if($this->input->post($tokenName) === $tokenValue) return true; // if this point is reached, token was invalid return false; } it gets token name from here where it checks inside $_SESSION variable <?php public function getTokenName($id = '') { $tokenName = $this->session->get($this, "name$id"); // Why can't it find token inside $_SESSION global? if(!$tokenName) { $tokenName = 'TOKEN' . mt_rand() . "X" . time(); // token name always ends with timestamp $this->session->set($this, "name$id", $tokenName); } return $tokenName; } I dont understand why it cannot find correct token and regenerates? Does session not last until browser closes? I can save other data to $session, and get it just fine, am I missing something?
  22. Hi, I am using pagination with ajax. When i click on any page number url not changing because of ajax call So when i click on page 3 and then click on page 7 i hit the back button it returns to page 1 because url never changing Here is my site: www.sediremlak.com For real estate sites if visitor find a property on page 4 and click on that property page. After click on back button site jumps to first page or home page not page 4 How can i fix this problem?
  23. I had situations come up that just seemed like AJAX was the right way to handle interactions with the ProcessWire server - pages with an element like a button or link that should cause an action to occur but shouldn't require a form or actually following a link - it should just take the action and only update the toggle (a checkbox in this case) when the interaction is completed. Another use case is with a large page on which there are multiple possible interactions. When the page is heavy enough that redrawing results in a less than optimal user experience then it's nice to be able to submit a form without having to redraw the page in order to update the relevant parts. So with that preamble, here's what I put together. I was going to try to clean it up a bit but that has prevented me from posting this so I figured it's better to post it and clean it up if there is any interest. You'll see references to the namespace whale - the name of our project - that would ultimately be removed. There are two major components - the PHP side and the client-side. On the PHP side there are two functional areas: 1. "wrapping" an entity to be inserted into the HTML on a page Wrapping (the function 'makeContainer()' puts a predefined wrapper around one of three types of objects: FormBuilderForm, InputfieldForm, or Template). The wrapper provides context and attaches classes that allows the client JavaScript code to find the wrapper and figure out what to do with it. // // define a function that makes a "form" of a single button. // function makeButton ($label) { // get the form $form = wire('modules')->get("InputfieldForm"); $form->attr('action', './'); $form->attr('method', 'post'); $submit = wire('modules')->get("InputfieldSubmit"); $submit->attr('id+name', 'submit'); $submit->attr('value', $label); $form->add($submit); return $form; } // wrapper function to set label on submit button function requestUserDeleteList() { return makeButton('Do it!'); } // // makeContainer wraps the rendered InputfieldForm in HTML so the client JavaScript can recognize it and handle // AJAX interactions with the server. // It returns the InputfieldForm object and the HTML to be inserted into the page. Note that makeContainer // is in a different namespace so it requires the function name must be qualified with the \ProcessWire prefix. // list ($form, $deleteUsersHTML) = ajax\Request::makeContainer('do-something', '\ProcessWire\requestUserDeleteList'); 2. helping with the processing of an AJAX request that is submitted to the page. Helping with the AJAX request - the code is invoked on page load and determines where there is a valid AJAX request from something it wrapped. It also allows messages to be returned, classes to be added or removed from specific elements, redirects to be executed, or even wholesale replacement of DOM elements (with plenty of caveats). It will even update a submit key so it is possible for the client to execute a single transaction multiple times. // get a new request object for the AJAX transaction $request = new ajax\Request(); // if it isn't formatted correctly handle the error if (!$request->isValidCall()) { return $request->echoError(); } // get the data and function-specific contents (Whale Ajax Context) $data = $request->data(); $wac = wireDecodeJSON($data['wac']); // if ($request->id('wants-newsletter')) { if (!ajax\Request::hasCorrectProperties($data, ['wac', 'value'])) { return $request->echoError(__('invalid call')); } // implement function here } else if ($request->id('another-function')) { // implement function here } // it didn't match any of the AJAX IDs implemented return $request->echoError('not implemented'); The client code requires jQuery and is packaged as three separate functions because both the form and template processing share a common core set of functions. My original intent was to only load the form or non-form code as needed but they're small enough that it really doesn't matter. See attachments for the Request class and the client code. There are many helper functions. Here is a kind of an unfocused extract that illustrates using the code with more context (from an internal sandbox page): <?php namespace ProcessWire; using whale\ajax; // include server-side code for making forms and processing them require_once './utility/ajaxform.inc'; // custom version of ProcessWire/wire/core/WireFileTools.php render() that returns the // template object, not the rendered HTML require_once './utility/get-file-template.inc'; // START AJAX submitted form processing - decodes the request and stores results in $aaform. $aaform = new ajax\Request(); // // this page handles multiple ajax calls so I check to see if it is valid once and then check IDs. // It's also possible to use $aaform->isValidCall('get-user-delete-list') to check specifically // for a specific AJAX ID. The ID is the name provided to Request::makeContainer() when the object // is wrapped. It's also possible to make calls to $aaform->id('get-user-delete-list') to check // for a specific ID. // // to create the forms/input elements that are submitted via AJAX start with: // Request::makeContainer('unique-name-on-page', object) // unique-name-on-page will become the ID of the element that wraps your object. // object - one of ProcessWire\InputfieldForm, \FormBuilderForm, ProcessWire\Template. // if ($aaform->isValidCall()) { if ($aaform->id() === 'get-user-delete-list') { $form = requestUserDeleteList(); // process using the form. the Request object will check to make sure it's the right type. if (!$aaform->process($form)) { return $aaform->echoError(); } // build new form with usernames for selections to delete. the function getUsersToDelete() // returns a user count and a function that will make the form that includes the users in // a list of checkboxes. list($usercount, $formmaker) = getUsersToDelete(); // this returns a replacement to part of the existing DOM. There are limitations but it // handles adding a form or replacing an existing form. if ($usercount === 0) { $replacement = '<div id="ajax-place">No users to delete</div>'; } else { // we pass the $formmaker function to makeContainer(). It returns the form and the // rendered wrapper and form. list($xform, $xhtml) = ajax\Request::makeContainer('do-delete', $formmaker); $replacement = '<div id="ajax-place">' . $xhtml . '</div>'; } // this makes sure the return is formatted so the client can handle it correctly. in // this case a replacement in the DOM is being returned. The first argument is the // selector, the second is the HTML to replace the selected element with. return $aaform->echoReplacement('#ajax-place', $replacement); } else if ($aaform->id() === 'do-delete') { list($usercount, $formmaker) = getUsersToDelete(); // process using the form returned by $formmaker. this will check to make sure it's // the right type of form. This abstracts FormBuilder forms and InputfieldForms. if (!$aaform->process($formmaker())) { return $aaform->echoError(); } // a bunch of logic where the checked users are deleted $deleted = []; $failed = []; $data = $aaform->data(); foreach($data as $name => $value) { if ($name === $value) { $user = wire('users')->get("name=$name"); $email = $user->email; // delete the user and try to get it again to see if the delete worked wire('users')->delete($user); $u = wire('users')->get("name=$name"); if (!count($u)) { $deleted[] = $email . " ($name)"; } else { $failed[] = $email . " ($name)"; } } } $deleted_users = $failed_deletions = ''; if ($deleted) { $deleted_users = 'deleted:<br/>' . join($deleted, '<br/>') . '<br/>'; } if ($failed) { $failed_deletions = 'failed to delete:<br/>' . join($failed, '<br/>') . '<br/>'; } $replacement = '<div id="ajax-place">' . $deleted_users . $failed_deletions . '</div>'; return $aaform->echoReplacement('#ajax-place', $replacement); } else if ($aaform->id() === 'contact') { // here a FormBuilderForm is being loaded if (!$aaform->process($forms->load('contact'))) { return $aaform->echoError(); } // this sends a notice back. the client will place it in a predefined notice area. // Request::makeContainer() will create an area for notices (or you can supply one). // It is also possible to return errors; notices and errors get different classes. $msg = ajax\Request::makeNotice('bruce says hi'); return $aaform->echoSuccess($msg); } else { // it was a valid form but it doesn't match any ID that this page knows about. return $aaform->echoError('what is this?'); } } // normal processing to render the initial page follows as it was not a valid AJAX post // that is handled by Request(). That's a lot of code, so I won't post anymore. If people have interest I'm happy to explain or provide other bits of code, like the extracted get-file-template.inc function. Wrapping a template is similar to wrapping a form except that only certain HTML elements are tracked and each are sent to the server when they are clicked on (technically it varies). It handles radio buttons, checkboxes, links, and buttons (radios and checkboxes on "change" and links and buttons on "click"). So when a checkbox is checked an AJAX call will be made so it can be acted upon by the server. @microcipcip, @ryan, @valan (sorry to any if this isn't interesting to you - I did a quick scan of what looked like semi-related AJAX posts). ajaxform.inc ajaxclient.js
  24. Trying to deploy a PW site to a client's hosting provider. Everything works as expected in development, but on the production host, certain AJAX requests fail. Here's what I'm seeing: 1) I have form on every page which is submitted via AJAX POST to the current page. No matter which page you POST to, it always returns a PW 404 page. 2) AJAX image uploads on the back end return a 200 or 302 for the original request, then spawn a GET request for the homepage. In trying to troubleshoot this, I have found that the host has both suhosin and mod_security installed. They've provided me with a local php.ini to test configuration changes. I've added the following to .htaccess (temporarily): # account-specific php.ini file <IfModule mod_suphp.c> suPHP_ConfigPath /home/[username] <Files php.ini> order allow,deny deny from all </Files> </IfModule> # disable mod_security <IfModule mod_security.c> SecFilterEngine Off SecFilterScanPOST Off </IfModule> In the php.ini file, I've set the following directives: suhosin.simulation = On always_populate_raw_post_data = -1 I've also set a specific directory for uploads: upload_tmp_dir = /home/[username]/tmp GD support is included. PW doesn't log any errors, even with $config->debug set to true. This is PW 2.7.3 on PHP 5.6.28. What else should I check?
  25. Hello, since hours i am struggeling to get an ajax call to an page field working. i think im doing something fundamentally wrong. The Idea is to load some html markup for larger screen sizes. If i put a .php file with the markup outside the 'site' folder it works fine with a basic XMLHttpRequest. But if i want to have the markup stored in a textfield on my 'settings page' within the template folder i have no success. I tried to follow some tips from this thread as well: my settings.php looks like this: <?php if($config->ajax) { $json = array( 'id' => $page->id, 'title' => $page->title, 'markup' => $page->markup ); echo json_encode($json); return; } and my inline javascript like this ( wrapped in enquire.js): <script type="text/javascript"> enquire.register('screen and (min-width: 700px)', { match: function() { var xhr = new XMLHttpRequest(); xhr.onload = function(){ if(xhr.status === 200){ data = JSON.parse(xhr.responseText); document.getElementById('hero').innerHTML = data.markup; } } xhr.open('GET', 'settings.php', true); xhr.send(null); }, unmatch: function() { } }); </script> <section id="hero"> </section> Like this, i am just getting a 404 , and if i try with a path i get a 403 forbidden. Im entirely confused, maybe someone can help. Thanks a lot.