Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation since 05/06/2023 in Posts

  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. 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
  5. 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
  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. APTUS

    Grid edit by default

    @kpcollier, thanks for your suggestion above. While I wasn't able to use your exact selector to get rid of the "flashing regular tabular report before grid edit," you gave me the idea and I was able to make it work with a different class selector. The CSS that I am using is shown below. Also, @oliverk, this may help you. I simply placed this CSS Style as the very first item in the header (to make sure it runs and hides the tabular report before anything else runs) and so far so good. When I open the datapage, there is a very short delay before the grid edit mode shows up, but fortunately only a blank white screen is now showing during that delay. <style> .cbResultSetTable { display: none !important; } <style>
    2 points
  15. 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
  16. You're a lifesaver, this is what I was exactly looking for! Thank you @Kronos
    2 points
  17. 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
  18. Aether

    Slow loading of forms

    You may want to check this cause of slowness as well:
    2 points
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. Hi Guys, Does anyone know how to change the standard Responsive design code (below) for gallery pages so that on trigger the number of columns is reduced not to one column (image) but two? I currently have a gallery page set up with three columns (images) i've wrapped a div round them and set it to width: 100%; so that the images adjust naturally as the screen size reduces, however once the @media clause kicks in the gallery reverts to one image at 100% and that is too big, i'd prefer the columns to reduce to 2 from 3 as set in the datapage. <!-- Responsive Code Begin --> <style> @media (max-width: 768px) { #gallery-single * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } #gallery-single table[id^="PageActionsCtnr"] { border-spacing: 0px !important; border-collapse: separate !important; } #gallery-single table[id^="PageActionsCtnr"] td { display: block; width: auto!important; float: left; text-align: left; padding: 1px !important; } #gallery-single table[id^="PageActionsCtnr"] td div { text-align: left !important; } #gallery-single table[data-cb-name^="cbTable"] { border-spacing: 0px !important; border-collapse: collapse !important; margin-bottom: 7px; } #gallery-single table[data-cb-name^="cbTable"] td { display: block; width: 100% !important; float: left; text-align: left; margin-top: 10px; } #gallery-single table[data-cb-name^="cbTable"] td div { text-align: left !important; } #gallery-single table[data-cb-name^="cbTable"] td > div > div[name^="RACtnr"], #gallery-single table[data-cb-name^="cbTable"] td > div > div[name^="RACtnr"]:hover { vertical-align: middle !important; white-space: normal !important; background: transparent !important; overflow: hidden !important; display: inline-block !important; padding: 0px !important; margin: -6px 12px 8px 12px !important; line-height: 0px; position: static !important; width: auto !important; opacity: 1.0 !important; } } </style> <div id="gallery-single"> <!-- Responsive Code End --> Any help will be appreciated.
    1 point
  27. I managed to build a search and result app. But when I receive the results page, it is so wide to print correctly. Is there any way to insert an option that shows me that same list in a \"printer friendly format page\"? Thanks
    1 point
  28. Caspio’s documentation on this is so vague it is of no help whatsoever. They say “Caspio Bridge DataPages can receive query string values from external sources through either POST or GET methods.†Unfortunately the whole reason I am using Caspio in the first place is that while I know html quite well, I don’t know scripting or form processing at all - so I have no idea how to do this. Can anyone help? Here is what I’m trying to do… We are about to launch a marketing campaign that offers a gift card to those who sign up for our program as a result of the campaign. So we need to track the customers who come directly from the campaign webpage vs all others. To do this I have added a “source†field to my table and set it up to receive a parameter @source. Then in my campaign webpage I appended “?source=campaign†to the URL in the link to the sign up page. I thought this would pass the value “campaign†to the source field for all sign-ups that came directly from that page (presumably all other sign ups would have a blank source field). But the parameter is not being passed. Any ideas? Oh, and to complicate things a little, the sign up form is in an iframe on a page in our MOSS website and I don’t know if the iframe is part of the problem.
    1 point
  29. Hi @WatashiwaJin I've been messing with this to try to get it to work. document.getElementsByName('Bulk').onclick = function(){myFunction()}; function myFunction() {document.getElementById('InsertQuantity').value = "100"; }; This removed the onclick error. But it still doesn't fill Quantity to 100. *Edit I might've done too much. Putting [0] after 'Bulk' creates the onclick error. Taking it out prevents the error but still does not work. I tried to log a message through the console in the function and didn't get any response... so my function does not work lol. *Edit #2 https://howto.caspio.com/datapages/ajax-loading/ Found this in the online help article listed above. onload(), onsubmit() and other loading events are no longer supported. You must use the Caspio built-in event handlers, as described below on this page. Working on trying to use a Caspio event handler to substitute the onclick function.
    1 point
  30. Thanks but Zapier doesn't work for my needs. My app is public facing (b2b) and each customer has their own login that needs to be authenticated. Because Caspio doesn't have a way to import/export/Zapier data via an authenticated user (instead of the overall Caspio Account holder) there's no way for app users to connect their data to outside apps. Meaning a user cannot enter their own Zapier login/mapping and have it interact with their own specific Caspio data. I, as the CB account holder, would have to have my customer's login info for Zapier and other apps, which is ridiculous. To me this is the biggest limitation of Caspio and why it will not work for most SaaS applications- no ability for the end user to sync with their own outside accounts in other apps.
    1 point
  31. Hello @Scott17, This is my method on getting the ID of the fields or in your case the button. Just follow these steps: For your case, click the button to get the ID. And after that insert the ID to the code. Try and test it and that should be fine
    1 point
  32. 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
  33. 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
  34. 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
  35. Oh nevermind, I tried adding this to the header of my DataPage setup and voila, it wraps the header cells of my Tabular Report.
    1 point
  36. Casey

    onbeforeunload problems

    Ok, I solved it for myself. It isn't working within the datapage footer but when I put the script on the footer of the webpage that I've embedded into the datapage it seems to work just fine
    1 point
  37. GoCubbies

    Caspio User logs

    Lynn, The User Logs do not appear as a data source under 'All Assets', so you would need to download the logs, then import them in order to reference by a DataPage
    1 point
  38. Hi Elena, You can try this code: <script> var y=document.getElementById("InsertRecordCurrency"); y.onblur = function() {myFunction()}; function myFunction() { var x = y.value; y.value=CommaFormatted(CurrencyFormatted(y.value)); function CurrencyFormatted(amount) { var i = parseFloat(amount); if(isNaN(i)) { i = 0.00; } var minus = ''; if(i < 0) { minus = '-'; } i = Math.abs(i); i = parseInt((i + .005) * 100); i = i / 100; s = new String(i); if(s.indexOf('.') < 0) { s += '.00'; } if(s.indexOf('.') == (s.length - 2)) { s += '0'; } s = minus + s; return s; } function CommaFormatted(amount) { var delimiter = ","; // replace comma if desired var a = amount.split('.',2) var d = a[1]; var i = parseInt(a[0]); if(isNaN(i)) { return ''; } var minus = ''; if(i < 0) { minus = '-'; } i = Math.abs(i); var n = new String(i); var a = []; while(n.length > 3) { var nn = n.substr(n.length-3); a.unshift(nn); n = n.substr(0,n.length-3); } if(n.length > 0) { a.unshift(n); } n = a.join(delimiter); if(d.length < 1) { amount = n; } else { amount = n + '.' + d; } amount = minus + amount; return "$" + amount; } } </script> You just need change the highlited text in red with the exact name of the field.
    1 point
  39. If I am entering a new row and check "ConstructionContractavaiable" and add a PrimeContractor, ConstructionContractavaiable will get unchecked in the trigger below Table design is : You need to update inserted
    1 point
  40. You may insert the following java script in the html block: <script> var x=[@cbRecordIndex]; var y=[@field:Your_field]; document.write(x*y); </script> Don't forget to change name of the field in the y variable Cheers!
    1 point
  41. In case anyone comes across this post, I did finally find an answer to my question. If I deploy the style to hide the column in the header of the WEBPAGE (vs header of the caspio page) then I can conditionally hide columns. Based on what page I am on, I hide different columns. Works perfect!
    1 point
  42. LWSChad

    Conditional Formatting Trick

    Check out this trick I stumbled upon. Put Caspio Variables into your Classes so you can use CSS to dynamically style your pages. (js not required) Div to dynamically format <div class="action[@field:action]"></div> css .actionCall { background-image: URL("../img/call.png"); } .actionEmail { background-image: URL("../img/email.png"); } .actionText { background-image: URL("../img/text.png"); } Dynamic Elements - load all options, and hide all but needed <a class="btnFile fileName[@field:FileName]" href="../some/dir/[@field:FileName]">Download</a> <a class="btnLink fileLink[@field:FileLink]" href="[@field:FileLink]">View File</a> css /*-- when [@field:***] is null, hide the div --*/ .fileName, .fileLink { display: none; } Hope this helps Think Easy CHAD -I know the Title of this Forum is "Ask....", but IDK where else to share stuff like this.
    1 point
  43. JavaScript Tips: Caspio Form Elements JavaScript is a client-side scripting language that is commonly used in HTML pages. JavaScript can be used in Caspio Bridge DataPages to extend capabilities outside the standard features. This page provides a guideline for referencing Caspio form elements using JavaScript. It is an advanced topic and you should have prior knowledge of JavaScript programming. Referencing Caspio Form Elements A common way to access a form element using JavaScript is by referencing the element's ID attribute using: - document.getElementById("id") The value in the field can be referenced using: - document.getElementById("id").value In the following sections, we will list the ID attributes for Caspio form elements in various DataPages. Submission Forms Text Field/Text Area/Dropdown/Checkbox/Listbox/Hidden: - InsertRecordFIELDNAME - FIELDNAME should be replaced with your field name. - For example: document.getElementById("InsertRecordFirst_Name") Radio Button: A radio button includes multiple options and each option has an associated ID. The ID is a name followed by a number: - InsertRecordFIELDNAMEX - X is the radio button option order, which starts at 0 and increments based on the order of each radio option. For example if your radio button has three options: Red, Blue, Green - Red is InsertRecordFIELDNAME0 - Blue is InsertRecordFIELDNAME1 - Green is InsertRecordFIELDNAME2 Virtual Field: cbParamVirtualX - X is the virtual field in the form, which starts at 1 and increments based on the number of the virtual fields in the form. - For example: document.getElementById("cbParamVirtual2") is referring to the second virtual field. Cascading Dropdown: Cascading dropdown and its parent dropdown are referenced by name since the ID changes on every page load. To reference cascading dropdown and its parent dropdown, use the following format: - document.getElementsByName("InsertRecordCompany_Name")[0] - Note that in the above format, the number 0 never changes. Display Only: These are not fields but span tags, therefore they don't have an ID. Details and Update Forms Details and Update Forms use the same rules and naming conventions as Submission Forms (in previous section), except that InsertRecord is changed to EditRecord. - EditRecordFIELDNAME - For example: document.getElementById("EditRecordFirst_Name") - Note that Virtual Fields and Display Only rules are the same as Submission Forms (in previous section). Search Forms Text Field/Text Area/Dropdown/Checkbox/Listbox/Hidden: - ValueX_Y - X is the form element order, which starts at 1 and increments based on the order of the element in the form. - Y is the criteria. It starts at 1 and increments based on the criteria order. It is always 1 if the field has no extra criteria. Radio Button: ValueX_Y[Z] - Z is the radio button option order, which starts at 0 and increments based on the order of the each radio option. Virtual Field: The rules are the same as Submission Forms (in previous section) Cascading Dropdown: This element should be referenced by name as explained in the previous section. The name of the element is ValueX_Y. Distance Search By ZIP/Postal Code: - Center of Search: cbDsField1 - Distance: cbDsField2 Distance Search By Coordinates: - Center Latitude: cbDsField1 - Center Longitude: cbDsField2 - Distance: cbDSField3 Authentication/Login Forms Text Field: xip_FIELDNAME - FIELDNAME should be replaced with your field name. Referencing Forms To access Caspio Forms/DataPages, you can reference the ID using: - document.getElementById("caspioform") Note that all Caspio DataPages share the same id as "caspioform". Therefore if there are multiple DataPages deployed on the same page, you need to reference them in a different way such as: - document.forms[X] X starts at 0 which refers to the first form on the page and increments based on the order. To access Caspio Authentication or Login Forms, use: - document.getElementById("xip_DataSourceName") - Replace "DataSourceName" with the name of data source used in your Authentication. Referencing Buttons Submit Button: Submit Update Button: Mod0EditRecord Delete Button: Mod0DeleteRecord Search Button: searchID Back Button: Mod0CancelRecord Login Button: xip_datasrc_DataSourceName - Replace "DataSourceName" with the name of data source used in your Authentication. Where to Place the JavaScripts In Forms, place your code in an HTML Block. The HTML Block should be the last element in the list. In Results, place the code in the Header or Footer unless you want the code to be executed for every record. Quick Tips If you need to reference an element that is not listed in the above document, try using F12/Developer Tools available from most browsers such as IE, Firefox, and Chrome. Developer Tools help web developers inspect the elements on the web page to find the related ID and also debug JavaScript code if necessary using the Console tab. For more information, check online for available tutorials using your browser's Developer Tools. In the meantime, you can see examples of JavaScript solutions in the community forum.
    1 point
  44. MayMusic

    Updating A Unique Field

    If it is an update for you should change document.getElementById("InsertRecordcombined_ID").value = res; to document.getElementById("EditRecordcombined_ID").value = res;
    1 point
  45. You can Edit the Style, enable Advanded options in the first wizard screen, go to settings screen, Forms/Details>>Fields>>Dropdown/Listbox Go to CSS tab and add Width:300px; To the .cbFormSelect class You can define a fixed value for dropdowns in the style but it applies to all the dropdown fields in the form so it does not work in your case. Dropdowns expand according to the length of each option so if you have long texts for each option, it is not good to use dropdown. Try the code below to set a fixed width for the dropdown, I hope that helps. <script> document.getElementsByName("InsertRecordFIELDNAME")[0].style.width = "300px"; </script> Replace FIELDNAME with the actual dropdown field name.
    1 point
  46. You can find your AppKey by clicking Deploy and you can then see the key in the code you would paste on your page. I would still like to get a response from someone at Caspio as to how to format the new window for printing and how to avoid having the new window go to the beginning of the application session.
    1 point
  47. It looks like a suitable soultion for me, but Im still missing something, I guess the "AppKey" have to be specific for my pages... where should I find that number? Thanks again.
    1 point
  48. I used the following code to open the new window:
    1 point
  49. Can you give us the link to your search and result so that we can take a look at it?
    1 point
×
×
  • Create New...