Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation since 04/26/2023 in all areas

  1. I've noticed that the topic of using AI in Caspio apps hasn't gotten much attention on this forum. This is surprising, given the big advancements in AI over the past few years. Recently we discussed this topic during the Partners’ Office Hours with @nikcaspio and decided to test the water sharing some ideas on the forum and hearing what the community thinks. I'd gladly give more examples in future posts if you find these thoughts helpful. The topic is almost endless, with so many ways to use AI. But let's start simple: WHAT IS DOCUMENT UNDERSTANDING We all know how Caspio can help create outgoing PDF documents based on database records. But what about incoming documents like invoices, receipts, delivery notes, or bills of lading? These often come as files or even paper documents. Here, we have to do the opposite of document creation. We need to take the document and turn it into data. Usually, someone has to enter this data manually into Caspio, right? Well, that was the old way Things have changed. To show what's possible now, I made a short video using our Caspio app, where I upload a JPEG file with an invoice, and the system extracts the necessary data from it and put it into Caspio: https://www.loom.com/share/d21eb53d9b5a4540a6282a56d8d47f0e Is it magic? No, it's simply artificial intelligence. And a pretty inexpensive one. I paid only a few cents to our "digital elves" for assisting me in recognising these documents in the video. The cost of human labor would most likely be much higher. TECH STACK Of course, you first need a Caspio plan with REST API and Zapier Integration. The Professional plan offers it, but you may buy it as an add-on to smaller plans. In this example, we used Google Document AI's Invoice Processor to do the work. It has some limits but is a good place to start and see quick results. And you can overcome these limits with more advanced methods. However, for this demo, it's more than enough. At first, I considered using Zapier for this demo because it was easy. But I know many people here find Zapier too expensive. So, we chose a more budget-friendly option. We used the newly introduced Caspio Webhooks and Make (Integromat) as middleware. This is just an example; you can use any technology you like. MAIN COMPONENTS: To make Document Understanding work, you need three main parts: OCR (Optical Character Recognition): This tech changes a binary file (like a photo taken with your phone) into text. Text Extractor: This tool looks for specific info (like invoice numbers or dates) and gives it back in an organized way. Middleware: This is the connector between Caspio and AI services. It takes a file from Caspio, sends it for processing, returns the data, and puts it into a Caspio table. GOOGLE DOCUMENT AI Google Document AI can do tasks #1 and #2. You can learn more about this service here and here's how to set it up. Of course, Google Document AI is not the only option, but it's an excellent place to start. Once you know its limits, you'll know how to work around them. There are many ways to do this. For example, in most of our projects, we use Google Document AI mainly for OCR. Then, we process the text with a tool from OpenAI, the creators of ChatGPT. But that's more advanced, and I don't want to make things too complicated in this article. MIDDLEWARE Google Document AI comes with a robust and well-explained API. But it needs incoming calls to have a particular format and gives back answers in its own format. If you're already using Zapier with Caspio, you might find it the easiest option, and you probably know how to use it. If so, skip to the Make Scenario section below for extra ideas. But if you're not using Zapier, stay with me. This post will focus on Caspio Webhooks as a more budget-friendly choice. Webhooks are good, but they have a limit: they're set in a standard way and don't allow much change. So, you'll need another tool to take the webhook call from Caspio and change it into a format that Document AI understands. There are many choices for this, and it's a big topic that could have its own set of detailed articles. For this demo, we'll use Make (Integromat). We find it even better than Zapier, but cheaper. They also have an excellent free plan that might be enough for smaller projects. Finally, Caspio has built a connector for Make, making it easy to set up, even if you're not an API expert. CASPIO CONFIGURATION To keep it easy, we turned on the FileStor option. This lets us get the file from Caspio using a direct link. I know this isn't the most secure way to handle files, but that's not the main point of this article, so I hope you understand. We also made a table and a few data pages in our demo app. These let you create a record and upload a file. Next, we set up a Caspio webhook that starts when a new record is added (Insert event). This webhook sends the call to Make's incoming webhook. Finally, we made a Web service profile so Make can talk to Caspio using REST API and return the results to the table. All these steps are well-covered in the Caspio manual, and there are also good video tutorials. So, I won't go into the details here. (If you need those, just ask in the comments, and I can post the links.) SCENARIO IN MAKE The process has just six steps in order: An incoming webhook that waits for calls from Caspio. A 10-second pause to give Caspio time to send the file to FileStor. A simple GET request to get the file from FileStor. Some minor changes to the data to get it ready for Document AI (details below). A call to Document AI to send the prepared data. A standard Caspio connector that puts the data back into the Caspio table. If you're new to Make or APIs and find all of this confusing, let me know if you'd like more details. I intended this post to be an outline rather than a step-by-step guide. However, I can explain things further if needed. It's not rocket science. But If you're familiar with Make or at least REST API, most of these steps should be pretty straightforward. The only part that might need some extra attention is step #4, where we set up headers and converted the binary file to base64. Here's how we set it up: If you've done everything right, you should be good to go! Not too hard, right? MORE ON THE TOPICS In this post, we've just touched the tip of the iceberg on Document Understanding. It's a big topic that could fill many more articles and take hundreds of hours to make a really deep dive. This is especially true for understanding complex documents like contracts or unstructured text documents. And the real magic happens when you move past ready-made solutions and train AI models with your own data. Learning all this can be a fun adventure, and the possibilities are endless. However, even with simple setups, AI can be a game-changer for many businesses. It can save time on dull tasks and let your team focus on more important work instead of just copying data from paper to an app. But AI isn't just about Document Understanding; it has lots more to offer. Here are a few more examples: Semantic Search: This isn't just looking up keywords; it understands the meaning of your question, irrespective of the words you choose. This can be useful for Knowledge Management and Customer Support apps. Voice-to-Text: Turn your video calls into text, summarize what was said, and add it to your Caspio app as follow-up notes. This is good for CRM, Project Management, Knowledge Management, and Recruitment apps. Classification of Incoming Requests: Automatically sort new requests and applications based on its content. This can help with CRM, Customer Support, and Recruiting apps. And there's much more. FINAL REMARKS I must say, this article ended up being longer than I first thought it would be. So, big thanks to you for sticking with me till the end! If anything was unclear or you ran into issues, please feel free to leave a comment or write me a DM if you prefer. I'll do my best to help you out. Now, I'd really like to hear your thoughts. Did I explain it well, or did it just confuse you? Does it make sense for you? Are you interested in this topic at all? Do you want to see more articles like this one? If so, what would you like me to focus on? More technical details? Real-world examples? A different writing style? I welcome your feedback and constructive criticism. Thank you!
    3 points
  2. Hi @ccxc007, I checked this with the support team a while ago and the answer was that this option will be added in future releases. Hopefully, it will be added in the next release. We`ll see
    3 points
  3. Just for future reference, questions like these can be quickly answered by ChatGPT, Bard, Bing, or other AI tools. I just put the question as you wrote it in ChatGPT and it gave me @ianGPT's answer in just a few seconds. It is very helpful and can be used for many coding questions.
    3 points
  4. Figured it out guys. Thank you much for this most valued and helpful assistance. How on earth you guys know this coding stuff is beyound me, it makes my brain melt! :-) Thank you again, it works like a charm!! % STARS!! Best, David
    2 points
  5. Hi everyone, So i've been playing around with how can i make gallery reports look more appealing especially when the bulk edit/delete options are enabled because i find it a bit clunky and dated: And I have come up with some code (a combination of CSS and JavaScript) that can make it look more sleek (in my opinion) so that it'll look like this: For the CSS (to be placed in the header): <style> form[action*='[@cbAppKey]'] .cbResultSetListViewDataLabel{ display: none !important; } form[action*='[@cbAppKey]'] [class*='cbReportBlock']{ border-radius: 20px !important; padding: 10px !important; } form[action*='[@cbAppKey]'] [class*='cbReportBlock']:hover{ background: #f5f5f5; cursor: pointer; } form[action*='[@cbAppKey]'] [class*='cbReportBlock'] [class*='selectedturn']:hover{ background: red !important; cursor: pointer; } .cbActionPanel ul{ align-items: normal !important; } .cbActionPanel li:nth-child(1){ align-items: baseline !important; display: flex !important; margin-top: -2px; } .cbResultSetListGallerySelectAllActionLink{ margin-left: 5px; } #actioncontainer{ padding-top: 10px; visibility: hidden; } #actioncontainer * { font-size: 15px !important; } .selectedturn{ background: #D9D9D9 !important; } .cbResultSetListViewHeaderCheckBoxCell{ display: none !important; } form[action*='[@cbAppKey]'] [class*='cbBulkFormLabel']{ display: flex !important; align-items: center !important; } .cbActionPanel label, .cbActionPanel a{ color: black !important; text-decoration: none !important; } [action*='[@cbAppKey]'] [id*='PageActionsCtnr'] { visibility: hidden; justify-content: flex-end; } [class*="cbResultSetActionsContainer"]{ position: sticky; top: -10px; z-index: 1000; } </style> For the script (to be placed in the footer): <script> var cards = document.querySelectorAll("form[action*='[@cbAppKey]'] [class*='cbReportBlock']"); cards.forEach(function(card, i){ card.addEventListener('click', function() { card.querySelector("input[type='checkbox']").click(); if(card.classList.contains("selectedturn")){ card.classList.remove("selectedturn") if(document.querySelectorAll("form[action*='[@cbAppKey]'] [class*='cbReportBlock'] input[type='checkbox']:checked").length == 0){ document.querySelector("[action*='[@cbAppKey]'] [id*='PageActionsCtnr'] ").style.visibility = "hidden" } }else{ card.classList.add("selectedturn") document.querySelector("[action*='[@cbAppKey]'] [id*='PageActionsCtnr'] ").style.visibility= "visible" } }, false); }) var selectallcheckbox = document.querySelector("[action*='[@cbAppKey]'] [title='Select All']"); selectallcheckbox.addEventListener('change', function() { var alloptions = document.querySelectorAll("form[action*='[@cbAppKey]'] [class*='cbReportBlock']"); if (this.checked) { alloptions.forEach(function(option){ option.classList.add("selectedturn") }) } else { alloptions.forEach(function(option){ option.classList.remove("selectedturn") }) } }); </script> Using these code, instead of having checkboxes on each of the record "card" as i'd like to call it, the entire card becomes the checkbox which I think makes it more user-friendly and interactive and modern. The bulk options also appear only when there's a record chosen, but if there werent any chosen, it is hidden, which lessens the visual clutter of the datapage.
    2 points
  6. For some reason, it is working for me. Here is what I did. In the Header (check the value where you put your BMI field): <script> 'use strict'; class linearBarGauge { /** * DOM element object */ element; /*** Options object given from user*/ options = {}; /** * Constructor, gets the selected element and the options given to generate the bar onto the page * * @param element * @param options * @return Object barSlider */ constructor (element, options = {}) { // start with a 0 to 100 percent this.inputLow = 0; this.inputHigh = 100; this.element = element; if (!this.constructor.isEmpty(options)) { this.options = options; } else { // default options this.options = { width: 300, // canvas width height: 10, // bar height outerHeight: 50, // canvas height range: { // range that the graph would start and end start: 1, end: 5 }, // optional additional segments /*segments: [ { start: 1, end: 3, colour: '#32CD32' }, { start: 3, end: 4, colour: '#FFA500' }, { start: 4, end: 5, colour: '#FF6347' } ],*/ segments: [ [20, '#32CD32'], [50, '#FFC107'], [80, '#FF6347'] ], value: 22 // I changed this to a static number since I don't have a field readily available to work with this. Maybe try parseFloat([@field:BMI]) } } this.init(); return this; } init () { // create and append the canvas element this.canvas = document.createElement('canvas'); this.canvas.width = this.options.width; this.canvas.height = this.options.outerHeight; this.canvas.className = 'lineGaugeCanvas'; this.element.append(this.canvas); // create the canvas this.drawCanvas(); if (this.options.value) { this.drawPointer(this.options.value); } } /** * Creates the range of */ translateRange(Input , inputHigh , inputLow , outputHigh , OutputLow) { inputHigh = inputHigh ? inputHigh : this.inputHigh; inputLow = inputLow ? inputLow : this.inputLow; outputHigh = outputHigh ? outputHigh : 1; OutputLow = OutputLow ? OutputLow : 0; return ((Input - inputLow) / (inputHigh - inputLow)) * (outputHigh - OutputLow) + OutputLow; } drawCanvas () { let stops = this.options.segments; // setup drawing context let ctx = this.canvas.getContext("2d"); // define the gradient let gradient = ctx.createLinearGradient( 0, 0, this.canvas.width, 0 ); // draw stops from an array // where every item is an array contains // the position and the color of the gradient for (let i = 0; i < stops.length; i++) { gradient.addColorStop( this.translateRange(stops[i][0]), stops[i][1] ); } // defines the fill style on canvas ctx.fillStyle = gradient; // create the y = 0 position on the canvas (currently at 2/3rds of the canvas' overall height) this.zero_y_postion = ((this.options.outerHeight - this.options.height) / 1.5); // draw the a rect filled with created gradient ctx.fillRect(0, this.zero_y_postion, this.canvas.width, this.options.height); return this; } /** * Draws the value pointer on the graph */ drawPointer (value,color) { // convert input value into a percent value inside our predefined range value = Number((value / this.options.range.end) * 100); // setup drawing context let ctx = this.canvas.getContext("2d"); const height = this.options.height ? this.options.height : 10; ctx.strokeStyle = color ? color : '#000'; ctx.lineWidth = 3; // draw line indicate a value ctx.beginPath(); const start_x = Number(this.translateRange( value, this.inputHigh, this.inputLow, this.canvas.width, 0 )); ctx.moveTo( start_x, // x1 this.zero_y_postion // y1 ); ctx.lineTo( start_x, // x2 this.zero_y_postion + height//y2 ); ctx.stroke(); this.drawValue(start_x); return this; } /** * Draw value box and number * * @param {Number} start_x The starting x position */ drawValue (start_x) { const rect_w = 45; const rect_h = 35; // get the starting x point of the rectangle const rect_x_start = start_x - (rect_w/2); let ctx = this.canvas.getContext("2d"); // add the rectangle with the value ctx.lineWidth = 2; this.roundRect( rect_x_start, this.zero_y_postion - rect_h - ctx.lineWidth + 1, rect_w, // w rect_h, // h 8 ); // add text const font_size = 25; // in pixels ctx.fillStyle = "red"; ctx.font = font_size + "px sans-serif"; ctx.fillText( this.options.value, Number(rect_x_start + ((rect_w - font_size) / 4)), // center the text inside the rectangle this.zero_y_postion - (rect_h - font_size) + (ctx.lineWidth / 2) // get y = 0 position, move to ); } /** * Draws a rounded rectangle using the current state of the canvas. * If you omit the last three params, it will draw a rectangle * outline with a 5 pixel border radius * Method from : https://stackoverflow.com/a/3368118 * * @param {Number} x The top left x coordinate * @param {Number} y The top left y coordinate * @param {Number} width The width of the rectangle * @param {Number} height The height of the rectangle * @param {Number} [radius = 5] The corner radius; It can also be an object * to specify different radii for corners * @param {Number} [radius.tl = 0] Top left * @param {Number} [radius.tr = 0] Top right * @param {Number} [radius.br = 0] Bottom right * @param {Number} [radius.bl = 0] Bottom left * @param {Boolean} [fill = false] Whether to fill the rectangle. * @param {Boolean} [stroke = true] Whether to stroke the rectangle. */ roundRect (x, y, width, height, radius, fill, stroke) { let ctx = this.canvas.getContext("2d"); if (typeof stroke == 'undefined') { stroke = true; } if (typeof radius === 'undefined') { radius = 5; } if (typeof radius === 'number') { radius = {tl: radius, tr: radius, br: radius, bl: radius}; } else { var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0}; for (var side in defaultRadius) { radius[side] = radius[side] || defaultRadius[side]; } } ctx.beginPath(); ctx.moveTo(x + radius.tl, y); ctx.lineTo(x + width - radius.tr, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); ctx.lineTo(x + width, y + height - radius.br); ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height); ctx.lineTo(x + radius.bl, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl); ctx.lineTo(x, y + radius.tl); ctx.quadraticCurveTo(x, y, x + radius.tl, y); ctx.closePath(); if (fill) { ctx.fill(); } if (stroke) { ctx.stroke(); } } static isEmpty(obj) { for(let key in obj) { if(obj.hasOwnProperty(key)) return false; } return true; } } </script> In the HTML Block: <div class="barGaugeWrapper"></div> <!-- Make sure you have an element with a class of "barGaugeWrapper" for the bar gauge to append to --> <script> let el = document.querySelector('.barGaugeWrapper'); let linearGauge = new linearBarGauge(el, { width: 400, height: 10, outerHeight: 100, range: { start: 0, end: 100 }, segments: [ [10, '#FF6347'], [30, '#FFC107'], [50, '#32CD32'], [70, '#FFC107'], [90, '#FF6347'] ], value: 22 }); </script> And in the footer, I included the canvas CDN: <script src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    2 points
  7. You can also check our How-To article for more information about Authentication: https://howto.caspio.com/authentications-and-connections/authentication/authentication/ For Failure Message: https://howto.caspio.com/authentications-and-connections/authentication/authentication/#:~:text=configure the following%3A-,Failure message,-- This message is
    2 points
  8. Hi @PatataChip, you can customize the message displayed when authentication fails. Go to your Authentication then below click the Advanced Settings and select Failure Message.
    2 points
  9. Did I explain it well, or did it just confuse you? You explained this very well, in a way that I imagine gets a lot of us excited about the potential of AI and how it can already help us out. Your article is very easy to follow, and the steps you laid out are great! Does it make sense for you? 100%! I think even novice developers would be able to follow this article with little confusion. Are you interested in this topic at all? Very interested, both in the different ways AI can be used to help developers and users with Caspio. This Document Understanding solution is one that I believe many of us will appreciate. Do you want to see more articles like this one? Yes, please! If so, what would you like me to focus on? More ways AI can be integrated into Caspio. I am under the belief that this is the next big change for cloud computing and "low-code" solutions. More technical details? I think what you have included here in this article is great - an overview and quick tutorial on how to set this up. If more technical details are warranted, there is always the comments section or DMs! Real-world examples? A few scenarios that I can think of that would greatly increase our experiences on Caspio, but might be a bit farfetched at the moment: Database Query Assistance, where users can input search terms such as "SUV" and have an output of values such as "Audi Q3, BMW X5, Chevrolet Tahoe" for rental car databases (thanks, @BenjaminS!) or perhaps "Kawneer 451" to spit out different glazing systems that utilize that style of storefront. User Training, where AI can scan through materials and generate training materials and methods to help kickstart users on how to use an application, or to enhance the onboarding process of new employees. Custom Application and Data Modeling/Schema Design - Share the guidelines for your next application and have AI give you a head start by creating the tables and datapages needed to get you started on your development. A different writing style? The way the article is laid out is great, in my opinion. Lastly, THANK YOU for getting the ball rolling on topics like this. I think there are a lot of people here like myself that have been waiting for advancements to AI and how it can help us and our users, but weren't sure how to get started or do anything like this. Your Document Understanding solution opens my eyes on different ways we can incorporate AI, even if it isn't a solution directly from Caspio itself! It has been a while since I last felt some excitement about the Caspio sphere and future ahead.
    2 points
  10. Hi, this isn't a question; I just wanted to share the following information: I was looking for a solution to synchronize my text input field and my calculated field only when the values of the calculated field change. Here's how I achieved this: <script> document.addEventListener("DataPageReady", function() { // Get the text input field const inputField = document.getElementById("InsertRecordfieldc"); // Add a change event listener to all elements whose IDs start with "cbParamVirtual1" document.querySelectorAll('[id^="cbParamVirtual1"]').forEach(function(calculatedField) { calculatedField.addEventListener("change", function() { // Update the text input field with the value of the changed calculated field inputField.value = calculatedField.value; }); }); }); </script> 1.) First we need the Caspio event listener DataPage ready. This code sets up an event listener that waits for the "DataPageReady" event to occur. 2.) Since the calculated field input field id is dynamically change we use a query selector and use a loop to select all id's that start with "cbParamVirtual1" or the id of your field which could varies depending on the number of your virtual field. 3.) Inside the "change" event listener function, inputField.value = calculatedField.value; is used to update the value of the text input field (InsertRecordfieldc) with the value of the changed calculated field (calculatedField). Essentially, it's synchronizing the value of the text input field with the value of the dynamically changing calculated field. Hope this helps!
    2 points
  11. Hi @Connonymous You can set up filters on your report DataPage to filter out records based on external parameters: https://howto.caspio.com/parameters/tech-tip-custom-filter-elements/ So you will have Criteria 1 and Criteria 2 filters that accept Criteria_1 and Criteria_2 external parameters. Then, using iframe deployment, you can deploy the same DataPage 9 times and each time pass different values to Criteria 1 and Criteria 2 filters via query string in the iframe source. For example, <iframe src="DATAPAGEURL?Criteria_1=A&Criteria_2=A"></iframe> <iframe src="DATAPAGEURL?Criteria_1=A&Criteria_2=B"></iframe> <iframe src="DATAPAGEURL?Criteria_1=A&Criteria_2=C"></iframe> And so on. If you need to add additional search filters on top of these Criteria 1 and Criteria 2 so that they accept dynamic values based on search input, additional JavaScript will be required. The flow can be summarized as follows: 1. You create a submission form DataPage that will act as a search form. This submission form should have only virtual fields, and the destination after submission should be the same page if you want search results to be below the search form. 2. JavaScript is needed to listen to "FormSubmitted" event. JavaScript will collect values from search input and add additional query string parameters with these values to source URLs of iframes. For example, if you need to filter out records based on department after search submission, the iframes will look something like this: <iframe src="DATAPAGEURL?Criteria_1=A&Criteria_2=A&Department=ValueFromSearchInput"></iframe> <iframe src="DATAPAGEURL?Criteria_1=A&Criteria_2=B&Department=ValueFromSearchInput"></iframe> <iframe src="DATAPAGEURL?Criteria_1=A&Criteria_2=C&Department=ValueFromSearchInput"></iframe>
    2 points
  12. Hi @RonAnderson, kpcollier provided the correct solution. To hide the field we need to listen for the value change in the Cascading Text field (eventListener is needed). I just wanted to add that if the Refrigerant field is a Cascading dropdown it is an input tag but not the select, so we need to reference it in a slightly different way. The code should work on the Submission form if you select elements with the prefix 'InsertRecord': <script> document.addEventListener('DataPageReady', hideDropdownHandler); let yesNoField = document.querySelector('input[id^="InsertRecordRefrigerant"]'); function hideDropdownHandler(){ yesNoField.addEventListener('change', hideField); document.removeEventListener('DataPageReady', hideDropdownHandler); } function hideField() { const refrigerantLabel = document.querySelector('label[for="InsertRecordRefrigerant_Gas"]'); refrigerantLabel.style.display = (yesNoField.value === "No") ? "none" : "block"; const refrigerant = document.querySelector('select[id^="InsertRecordRefrigerant_Gas"]'); refrigerant.style.display = (yesNoField.value === "No") ? "none" : "block"; } </script> And on the Details page if you select elements with the prefix 'EditRecord': <script> document.addEventListener('DataPageReady', hideDropdownHandler); let yesNoField = document.querySelector('input[id^="EditRecordRefrigerant"]'); function hideDropdownHandler(){ yesNoField.addEventListener('change', hideField); document.removeEventListener('DataPageReady', hideDropdownHandler); } function hideField() { const refrigerantLabel = document.querySelector('label[for="EditRecordRefrigerant_Gas"]'); refrigerantLabel.style.display = (yesNoField.value === "No") ? "none" : "block"; const refrigerant = document.querySelector('select[id^="EditRecordRefrigerant_Gas"]'); refrigerant.style.display = (yesNoField.value === "No") ? "none" : "block"; } </script>
    2 points
  13. Hello @Connonymous, As for the Triggers, you are correct, you need to take into account the #inserted table. It is better to use the following approach: 1) If you already have data in the tables, you may use a Task to populate the additional table with the current data. For example, I have this table as a main table: And this one as an additional table to use to create a Chart: This Task will populate the Count field: 2) After that, you may use Triggers to re-calculate the Count field. Trigger example that works on Insert: Trigger example on Delete (work for Inline delete, requires a Loop for bulk delete) Trigger example on Update (work for Inline edit, requires a Loop for bulk edit)
    2 points
  14. Hi kpcollier, Try this: LastDayofMonth will be equal to 30 for the date in the screenshot (June 7th 2023), but if you change it to, say, February 20th 2023, LastDayofMonth will be 28. P.S. TBH I'm not sure if all variables are required, I just tested until it worked
    2 points
  15. You're a lifesaver, this is what I was exactly looking for! Thank you @Kronos
    2 points
  16. Use LIKE CASE WHEN [@field:RequirementType] LIKE '%Ambulatory%' and [@field:Number_Of_Weeks] = 4 THEN 4 WHEN [@field:RequirementType] LIKE '%Ambulatory%' and [@field:Number_Of_Weeks] = 2 THEN 2 END The % is wildcard that defines the position of the string that you are looking for. Example : 'Ambulatory%' searches for values that starts with Ambulatory. '%Ambulatory' searches for values that ends with it. Adding it on both sides checks for the value in any position.
    2 points
  17. Aether

    Slow loading of forms

    You may want to check this cause of slowness as well:
    2 points
  18. Aether

    Slow loading of forms

    Hello @KG360 - What I can suggest is for you to partition your form and separate them on a different submission to create a "Multi-step form" so that the loading the page won't be that heavy to load. Refer to the article below: https://howto.caspio.com/tech-tips-and-articles/how-to-create-a-multi-page-form/
    2 points
  19. Ilyrian

    Append vs Replace

    Hi @roattw Replace: Overwrites the data of an existing table and load the new data into it. Any new fields are added to the table design. Append: Adds the new data to an existing table. Any fields that don’t exist in the current table design will be added. So, basically, when you use Replace it will overwrite all data, and if the data in the new file that you are trying to import is different from what you have in the table, it will overwrite all. When using append, it will keep your current data in the table, it will only add the new records that you have in the file.
    2 points
  20. RonAnderson

    Screen Layout

    Hi @cheonsa and @Tubby, I'm genuinely grateful for your responses. I eventually turned to Caspio for help and they very kindly came up with a solution very close to your response Tubby. Here's the code:- @media (max-width: 1048px) { div[class*="cbFormNestedTableContainer"]{ display:block !important; } } The code need to be placed in the HEADER. Kind regards Ron
    2 points
  21. Hi @beryllium, A standard way of achieving this without using JavaScript is below: Add a Header/Footer to your page. Enter following code to hide the existing Update button: <style> .cbUpdateButton { display: none !important; } </style> Add a new section and in it add an HTML Block. Enter following code to add a custom Update button: <div id="update" align="center"> <input type="submit" class="cbSubmitButton" value="Update" /> </div> Now add a Rule. It should look like below: Replace "Value" with your field name. I hope this helps. Regards,
    2 points
  22. Hi @gregcoiro, There is actually a possibility to Share the Task with the needed App. Once it is shared, you can see the app name in Properties. However, it looks like there is no bulk action so it should be done one by one for each Task.
    2 points
  23. In case you store all data in one table, for example: Then you may use this Task design: Maybe the Task can be optimized, however these Tasks work from my side. Hope this helps.
    2 points
  24. You can try the code below in the header of Configure Result Pages Fields screen: <style> .cbGridCtnr > .BodyCtnr > .Table{ width: 100%; } .cbGridCtnr > .HeadCtnr > .Table { width: 100%; } </style> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> <script> function openGridAutomatically(delay) { setTimeout( function() { var gridEditButton = $("[data-cb-name='GridEditButton']")[1]; if (gridEditButton) f_dispatchEvent(gridEditButton, "click"); }, delay ? delay : 500); } function f_dispatchEvent(v_element, v_type){ if(v_element.dispatchEvent) { //var v_e = new Event(v_type); var v_e = document.createEvent('MouseEvents'); v_e.initEvent(v_type, true, true); v_element.dispatchEvent(v_e); //new Event(v_type, {"bubbles":true, "cancelable":true}) } else if(v_element.fireEvent){ v_element.fireEvent('on' + v_type); } }; openGridAutomatically(); </script> width: 100%; should be changed to the width you have for your for your report page.
    2 points
  25. Hello @PotatoMato thanks a lot! I can now see the Submit button disabled. The logic seems to be working and even the HTML code is not displayed on the forms page.
    1 point
  26. Hi @PotatoMato I continued to read the article you suggested, and with a little determination and experimenting was able to get it to work. As it was a Single Record Update Page, I had to use the words EditRecord in front of the field name in the JavaScript to make it work <script type="text/javascript"> document.getElementsByName('EditRecordLastSelectedCompanyID')[0].onchange = function() {myFunction()}; function myFunction() { // submit the form if an value is selected setTimeout('document.forms["caspioform"].submit()',1000); } </script>
    1 point
  27. @PotatoMato Thanks for the reply. Unfortunately, the files are saved in Caspio, and FileStor doesn't seem too safe, so that first option is out. The second option seems a bit promising, but the viewer is showing "No Preview Available". I'll keep messing with this and see if I can get it to work.
    1 point
  28. Hi, @kpcollier. I suggest embedding the app parameter files on an HTML datapage using their article: https://howto.caspio.com/tech-tips-and-articles/tech-tip-embed-documents-in-datapages/, then use the datapage URL on your href with the target="_blank". Or use the Google document viewer to the href. Your link will be like this: <a href="https://docs.google.com/gview?url=[@app:File/]&embedded=true" target="_blank">Reliance CW Detail</a> -Potato
    1 point
  29. Hi, So, it's been a while without any replies. I got to researching and found the information to sort this out. The following page guide on how to have embedded iframes automatically resize to the appropriate height. Worked very well. Setting Automatic Height for Iframes in Embedded Data Pages - Caspio Online Help
    1 point
  30. Sorry, I can give a better answer later. But, first thought that comes to mind... Maybe try to include the Daily and DailyQual fields into the results page. I would create an HTML block (or use one of the ones you've got) and do something like in this article: https://howto.caspio.com/tech-tips-and-articles/customize-background-and-font-colors-in-report-datapage/ In the HTML block, create two elements with different IDs, but make sure the ID is something like 'daily[@IDField]' and 'dailyQual[@IDField]', where ID is the unique field in your table. You can hide these elements, or the entire column if you'd like. <span id="daily[@field:ID#]">[@field:MyFoodGroup_Daily]</span> <span id="dailyQual[@field:ID#]">[@field:MyFoodGroup_DailyQual]</span> Now the values are available for you to use in JS. var DailyGoal = [@field:MyFoodGroup_Daily]; var DailyQual = [@field:MyFoodGroup_DailyQual]; Would now become var DailyGoal = document.getElementById("daily[@IDField]").value; var DailyQual = document.getElementById("dailyQual[@IDField]").value;
    1 point
  31. Hello @jeffs88keys, Usually, the workaround is to create a separate table to store dates in one field. For example, there is a table to store events and each event has 3 different dates. It is possible to move the data from the existing records to a new table by a Task: Table example after the Task run: So, now it is possible to create a Calendar DataPage based on the Date field: For the new records, the same can be done by a Triggered Action.
    1 point
  32. @CoopperBackpack Something was broken in that DataPage. After I rebuilt it from scratch, everything works as it was supposed to. To sum up for the posteriority: CoopperBackpack's code as quoted above works! Cheers Benjamin
    1 point
  33. vidierre

    Caspio Server Performance

    @Flowers4Algernon thank you. At a first look I was going to answer that this is only an avaliability monitor, but when I read that it was a pingdom service I went deeper. On the initial panel if you click on your server some performace data can be retrived. As example for my server I can read this data for April: and an avarage response time of 681ms. Remember that I am on a Grow plan, it means shared resources. Everyone should remember the difference in terms of performance. Of course anyone who has worked on the performance of real data centers knows that these value are not the real user-perceived respinse time. But are a very solid base to make considerations. This strengthens my thinking about that CASPIO is an excellent choice for app adressing mid-size environments. Or at least the non-Company plans are excellent for an audience up to 50-100 users. I believe that the rule that "for greater users base you need more dedicated resources" is true for all platform. One can argue that Caspio Corporate plans are expensive or that the price progression has an excessive surge between the two worlds. But nothing about the offer completeness.
    1 point
  34. Hi @bookish You can use the following JavaScript to remove default details link from the List report: <script> if (typeof rowPosition == 'undefined') { const rowPosition = 1 const targetValue = 'Test1' const removeDetailLinks = () => { document.querySelectorAll('[class*="cbResultSetListViewTable"]').forEach(container => { if(container.querySelector(`.cbResultSetData:nth-of-type(${rowPosition})`).innerText.trim() == targetValue) { document.querySelector('.cbResultSetPanelColumnarRAContainer').remove() } }) document.removeEventListener('DataPageReady', removeDetailLinks ) } document.addEventListener('DataPageReady', removeDetailLinks ) } </script> Where rowPosition is the position of the row where [Field:Name] label and its value are displayed targetValue stores the value you would like to check against. So when the value of [Field:Name] equals to targetValue, the details link will be removed
    1 point
  35. Hello @BenjaminS, Thank you for the provided screenshot. I can only see 2 divs with the cbFormNestedTableContainer class. And I can see that 'grid' is added to many div elements. This should be the reason for the unaligned fields. The idea is to position multiple elements inside the grid parent elements. This is why those elements with the cbFormNestedTableContainer class are helpful. They are parent containers and inside them, we have hours and minutes fields. Could you test this code to see if this helps to align the Start and End time fields? <style> @media only screen and (max-width: 1024px) { div[class*='cbFormBlock8'], div[class*='cbFormBlock11'] { display: grid !important; grid-auto-columns: max-content; } } </style>
    1 point
  36. Hi @MariaT, Just to confirm, on the records displayed on your results page, do you want to display the record of who has the highest integer number in a field in the header of your results page?
    1 point
  37. Hey there! Caspio releases new enhancements to the Caspio Directories feature, check this out: - Ability to enhance user profiles with field values using Text64000, Number, Integer, and Yes/No data types - Ability to include user profile fields using data from a related table - Ability to customize the fields shown in the user list view Source: https://howto.caspio.com/release-notes/caspio-37-0/caspio-37-0/#:~:text=Enhancements to Caspio Directories
    1 point
  38. Hi @wimtracking2 You can add the following JavaScript code snippet to the Header or Footer of your DataPage: <script> if (typeof addObserver == 'undefined') { const addObserver = () => { new MutationObserver (mutations=> { for(let i=0; i<mutations.length; i++) { if(mutations[i].target.parentElement !=null) { if(mutations[i].target.parentElement.parentElement !=null ) { if(mutations[i].target.parentElement.parentElement.parentElement !=null) { if(mutations[i].target.parentElement.parentElement.parentElement.matches('#MultiSelectBox')) { addMultiSelectOptionSearch() break}}}} } }).observe(document.querySelector('body'), {childList: true, subtree: true}) } const addMultiSelectOptionSearch = () => { if (document.querySelector('#MultiSelectBox .cbFormMultiSelectText') == null || document.querySelector('#filterOptions') != null) return let firstMultiSelectOption = document.querySelector('.cbFormMultiSelectText') firstMultiSelectOption.insertAdjacentHTML('beforebegin', `<div class="cbFormMultiSelectText" style="outline: none; white-space: nowrap; overflow: hidden; margin-bottom: 25px;"><input type="text" id="filterOptions" style="position: absolute; width: 90%"></div>`) addFilterSelectOptionsListener(document.querySelector('#filterOptions')) } const addFilterSelectOptionsListener = (HTMLTextInput) => { if (HTMLTextInput==null) return let parentContainer = HTMLTextInput.parentElement.parentElement HTMLTextInput.addEventListener('input', (e)=>{ parentContainer.querySelectorAll('label').forEach(label=>{ if(label.innerText.toLowerCase().trim().includes(e.target.value.toLowerCase())) { label.parentElement.style.display = 'block' } else label.parentElement.style.display = 'none' }) }) } addObserver() } </script> Hope this helps
    1 point
  39. So I am searching for a solution in which I can copy a certain part from one data table to another. This is needed as I don't want to edit my main data table which contains all possibility's, I need to edit a different data table so I can use my main data table again later on. This is why I need a way to copy a part of the data table (the part with a certain name in the first column, there are multiple rows with the same name in the first column, but other data in other columns(example attached))to a different data table with a custom ID in the first colum.
    1 point
  40. Hello Everyone, In the recent release 34, Caspio introduced Directories (BETA) that could be used for authentication. You may refer to these links. Note that for now, it's currently available as a beta release to corporate-level plans. https://howto.caspio.com/release-notes/caspio-34-0/caspio-34-0/ https://howto.caspio.com/directories/directories-overview/directories-overview/
    1 point
  41. In addition to what @IamNatoyThatLovesYou said, I used the Shortcoder plugin to deploy DataPages on Wordpress. Basically what you will do is to put the Caspio deploy script in a Shortcoder. That will then generate a "shortcode" and you can copy that in your Code block.
    1 point
  42. Kurumi

    Copy text Button

    In addition to this, you can actually add more fields to the value. Like document.getElementById('in[@field:ID]').value="[@field:FIELD1]"+" "+"[@field:FIELD2]"; So it could copy all the data in every row. Sample below:
    1 point
  43. sandy159

    Multiple Search Fields

    Hi @wdandrewsshpg, If I understand you correctly, you are talking about the Keyword Search across multiple fields. You may check the article on how it can be implemented: https://howto.caspio.com/faq/reports-datapages/how-to-do-keyword-search-across-multiple-fields/ Feel free to update this thread if you were looking for something else or if you have questions. Regards
    1 point
  44. Barbie

    Blank value result

    Hi @Batchini Can you try this formula instead: IsNull([@field:Field1], 0) + IsNull([@field:Field1], 0) I just added IsNull function. What it does is it replaces NULL with the specified replacement value, otherwise returns the result of expression. You may refer to this article as well. https://howto.caspio.com/function-reference/ - Barbie
    1 point
  45. Hello @OlivierDeNantes, 404 error message is a standard response code to indicate that the client was able to communicate with a given server, but the server could not find what was requested. I can see that Caspio script loaded fine but the error occurs on the dev.avenue-foch.com and WordPress was not able to find the requested page. Do you have any redirection occurring on login? Check your Advanced options for any Authentication and verify that the pages for Logout Destination, Time Out & Redirection, and Login Redirection on success are valid pages. If using relative path, try a full URL. Also, if you are doing any type of redirection on login using App Parameters, check that the URL for these are valid as well. To troubleshoot deployment on the WordPress you may refer to the following articles: https://howto.caspio.com/troubleshooting/troubleshooting-deployment-in-wordpress/ https://howto.caspio.com/deployment/deploying-into-wordpress/ Feel free to update this thread if you have further questions. Regards, Sandy
    1 point
  46. Hello y'all, This solution is great. I have tested it. However, on my use case, I had dropdowns and textareas which were not affected by the reset. I just wanted to share an updated code that will affect such. <button type="button" onclick="resetForm()">Clear Form</button> <script> function resetForm() { var myForm = document.querySelector('form[action*="[@cbAppKey]"]').querySelectorAll('input:not([type=submit]):not([type=hidden]), textarea, select'); myForm.forEach(function(elem) { elem.value=""; }); } </script> [src="carl.js"]
    1 point
  47. If you want to know about SEO, first link your website to google search console and analytics. This will help you to know traffic to your website. Secondly do keyword research and rank those keyword. These things are very basic of SEO.
    1 point
  48. Essentially, there are two primary things that you should use to ensure your SEO is working and these are your Keyword Rankings and Traffic. You can check this on SEO tools like "aherf".
    1 point
  49. Hello @kwikcert, SEO has two major functions: crawling and building an index, and providing search users with a ranked list of the websites they have determined the most relevant. And basically there are also two main things that you should use to make sure your SEO is working and these are your Keyword Rankings and Traffic. You may want to check this articles for more info: https://howto.caspio.com/deployment/seo-deployment-directions/ https://howto.caspio.com/faq/deployment/seo-deploy https://howto.caspio.com/troubleshooting/deployment/troubleshooting-search-engine-optimized-seo-deployment/ Kind Regards, Luna <3
    1 point
×
×
  • Create New...