Jump to content

Jose Leviaguirre

Spotfire Team
  • Posts

    230
  • Joined

  • Last visited

  • Days Won

    13

Jose Leviaguirre last won the day on October 1

Jose Leviaguirre had the most liked content!

5 Followers

Recent Profile Visitors

827 profile views

Jose Leviaguirre's Achievements

Community Regular

Community Regular (8/14)

  • Great Content Rare
  • Reacting Well Rare
  • Problem Solver Rare
  • Dedicated Rare
  • Week One Done

Recent Badges

44

Reputation

18

Community Answers

  1. Hello @SravanthiJampala I updated the code, so the popup don't go off the screen. In any case, you can "restore" the popup position by switching back to the page. Here is the code for reference as well (click to expand) JavaScript /* place this in the html code <div class="JSPopup" id="myPopup"> <div>click to open popup (trigger)</div> <div>Popup title (pullable)</div> <div>Popup contents</div> </div> add this JavaScript to a text area. ***Only one per page*** */ (() => { let highestZIndex = 1000; // Start with a high z-index value class PopupManager { constructor(popupId, index) { this.popupId = popupId; this.index = index; this.draggingLibraryUrl = 'https://unpkg.com/draggabilly@3/dist/draggabilly.pkgd.min.js'; this.init(); } init() { this.loadDraggingLibrary().then(() => { this.createPopout(document.getElementById(this.popupId), this.index); }); } loadDraggingLibrary() { return new Promise((resolve) => { if (!document.querySelector(`script[src="${this.draggingLibraryUrl}"]`)) { const script = document.createElement("script"); script.src = this.draggingLibraryUrl; script.onload = resolve; document.body.appendChild(script); } else { resolve(); } }); } createPopout(popup, index) { let id = "JSPopup_" + index; let popupElements = popup.children; let popupTrigger = popupElements[0]; let popupTitle = popupElements[1].innerText; let popupContents = popupElements[2]; let isOpened = false; popupTrigger.style.cursor = "default"; let template = ` <div id="${id}" class="pullable ${isOpened ? "" : "hidden"}" style="z-index: ${highestZIndex};"> <span class="close"></span> <div class="contents"></div> </div> <style> .hidden { left: -1000px !important; } #${id} { border: 1px solid darkgray; position: fixed; padding: 5px; background: ${popup.style.background || "whitesmoke"}; box-shadow: 0 3px 11px 0 rgba(0,0,0,.16), 0 3px 6px 0 rgba(0,0,0,.16); border-radius: 6px; cursor: default; width: 300px; overflow-y: auto; max-height: 700px; } #${id} .close::after { content: "✖"; color: darkgray; float: right; margin-top: -15px; cursor: default; font-size: 20px; } #${id} .close { padding: 5px; } #${id} .close:hover::after { color: gray; font-size: 22px; } #${id} .contents { border: 0px solid darkgray; background: ${popupContents.style.background || "#FFF"}; margin-top: 3px; padding: 5px; min-height: 100px; } .DropdownListContainer.sf-element-styled-dialog.sfc-style-root.prevent-flyout-close { z-index: 99999999; } </style> `; popup.innerHTML = template; let popupBody = document.getElementById(id); popup.appendChild(popupTrigger); if (popupTitle.endsWith("***")) { popupTitle = popupTitle.slice(0, popupTitle.length - 3); popupBody.classList.remove("hidden"); } let handle = document.createElement("div"); handle.className = "handle"; let textNode = document.createTextNode(popupTitle); handle.prepend(textNode); popupBody.prepend(handle); let templateContents = document.querySelector(`#${id} .contents`); templateContents.style = popupContents.style.cssText; popupBody.style = popup.style.cssText; popup.removeAttribute("style"); templateContents.append(...popupContents.childNodes); popupTrigger.onclick = () => { let pp = document.getElementById(id); pp.classList.remove("hidden"); this.bringToFront(pp); }; handle.onclick = () => { let pp = document.getElementById(id); this.bringToFront(pp); }; popupBody.addEventListener('mousedown', () => { this.bringToFront(popupBody); }); document.querySelectorAll("#" + id + " .close").forEach((x) => { x.onclick = () => { x.parentNode.classList.add("hidden"); }; }); document.querySelectorAll(".pullable").forEach((aPopup) => { const draggie = new Draggabilly(aPopup, { handle: ".handle", containment: 'body' }); draggie.on('dragStart', () => { this.bringToFront(aPopup); }); }); } bringToFront(element) { highestZIndex++; element.style.zIndex = highestZIndex; } } // Usage const popups = document.querySelectorAll('.JSPopup'); popups.forEach((popup, index) => { new PopupManager(popup.id, index); }); })();
  2. @barchiel33 Thanks for the feedback! I did not publish the latest version! Pease try again.
  3. Just a bit, but requires some "hacking" please take a look at this thread
  4. Hello @PBR I think that all you need is to remove the selection and add a calculated column using RowID() dataTable = Document.Data.Tables["Events"] markings = Document.ActiveMarkingSelectionReference.GetSelection(dataTable) dataTable.RemoveRows(markings) The ID column is simply a calculated column. It's custom expression is: RowId() as [ID]
  5. Hello @barchiel33 We sincerely appreciate your thoughtful feedback on the Spotfire articles. Your points about code quality are well-received. We're actively working on improvements, including a possible code review process. Our articles aim to inspire, and we'll strive to communicate this more clearly. Thank you again for your valuable insights and we hope the articles are as valuable as your feedback. Thanks again. I made minor modification to the main article so it is clear to replace the entire <a> placeholder with the link action control. The font size and other attributes can be overridden directly on the markup itself: <div class="iconMenu" style="background:yellow;color:blue;font-size:50px;">
  6. Hello @Jon Orth 2 This is possible, but requires a little hack, which is not generally recommended as it involves tweaking the Spotfire internals, which can change from one version to another. Assume that you have a List Box Filter and you want to increase the font size in both, the values and search box ► html <span id="myFilter"> <SpotfireControl id="filter control" /> </span> JavaScript //this script injects a style tag to override default styles (()=> { const styleElement=document.createElement('style'); const styleElement.textContent = ` /* this selector takes care of the hint that says 'type to search in list' "*/ #myFilter .SearchInput::placeholder { color: white; /* Replace 'red' with your desired color */ font-size:12px; text-align:center; } /* this rule overrides selected items' "*/ #myFilter .sfpc-selected { background:yellow !important; color:black !important; } /* this one is for the searchbox input' "*/ #myFilter .sf-element-input { font-size: 18px !important; background:red; color:yellow !important; } /* this lst one is for the all the list items' "*/ #myFilter .sf-element-list-box-item { font-size: 18px !important; background:gray; color:white; }`; //injects the code to the text area myFilterElement=document.getElementById('myFilter'); myFilterElement.appendChild(styleElement); })() This approach is also not documented, so use it at your own risk. You need to understand that the SpotfireControls generate the html dynamically and it can be hard to find out all the variables that creates the filter. You might want to increase the padding or margin, but that will require more investigation to see which elements require to be change. I use the Developer's tools to find out the rules names so I can override them. If you are familiar with CSS, then you can explore and play with the values using the Developers Tools. This option is available from Tools > Development > Developer tools... or by pressing F12 if using Google Chrome. If you don't see that option on your client, then check the tools > options > Application > Show development menu option and restart the client. A shortcut that does not require a restart is to ctrl+alt+shift+F12 to bring up the DevTools. Once you have the DevTools, you can start exploring the guts of Spotfire. I encourage you to go to that page and watch the intro video from the previous link. It's fun and very informative.
  7. Add a tool to select columns even when in viewing mode How it works An Iron Python script updates the columns from a Table visualization with the selection from a Multiple Selection List Box. There are buttons to reset to the default view or from a pre-defined preset. Steps to create this tool Basic Functionality Step 1: Create a text area and add a List Box (Multiple Select). and assign a document property called dataTableColumnSelection Step 2: Click on the [Script...] button and add the following script so when the value of the document property changes, The script requires a table visualization parameter called 't1' IronPython Script from Spotfire.Dxp.Application.Visuals import TablePlot, VisualContent from Spotfire.Dxp.Data import DataPropertyClass #Script Parameters #t1 is a script parameter data table visualization #Script Settings multiSelectColumnsPropertyControl = "dataTableColumnSelection" #multiselect doc prop control dataTable = Document.Data.Tables.DefaultTableReference #reference to the default data table def updateColumns(tablePlotvisualRef): tablePlotVisual = tablePlotVisual=tablePlotvisualRef.As[VisualContent]() cols = tablePlotVisual.Data.DataTableReference.Columns tablePlotVisual.TableColumns.Clear() # 2.2 get document property selection = Document.Data.Properties.GetProperty(DataPropertyClass.Document, multiSelectColumnsPropertyControl).Value # 2.3 Parse columns from selection and add to tablePlotVisual for property in selection: for col in property.split(","): tablePlotVisual.TableColumns.Add(cols[str(col)]) updateColumns(t1) #updateColumns(t2) Step 3: Test the script. It should work adding or removing columns Create the reset button The reset button sets the table with a predefined number of columns Create a text area and add a List Box (Multiple Select). and assign a document property called defaultPreset. Select the columns you want to be as the default view You can now hide or delete the control. The document property should remain with the selected column values Add a button that triggers the following script. Document.Properties["dataTableColumnSelection"]=Document.Properties[selectedPreset] The selectedPreset is a script parameter to pass the name of the document property name. This script transfers the value from the defaultPreset to the dataTableColumnSelection Create different presets Presets are useful to create groups of different columns. The approach is similar to the reset button Create a 3 text area and add a List Box (Multiple Select). and assign a document property called preset1, preset2 and preset3. Make sure you also select some default columns for each preset Delete the 3 text areas, The preset1,2 and 3 document properties are created as String List data type Add a Drop-down list and assign a document property called presets. Link the preset document property so when it changes it runs the setPreset script. Set the selectedPreset script parameter to the presets property Create the save preset button This button updates the column settings saved on each of the presets from the dropdown. It copies the current dataTableColumnSelection values to the corresponing preset Create a button to update the selected preset and add this script called savePreset to it Document.Properties[preset4update] = Document.Properties["dataTableColumnSelection"] set the preset4update as a script parameter pointing to the presets property Create a popup Format the multiselect control to make it wider and accommodate for the column width and height as well as the buttons Follow the instructions from here
  8. Here are different ways to enhance a Dropdown-list Property Control. It requires a new filtering scheme just to update the document property A simple Dropdown-list property control looks like this: The map below is limited by a document property expression: upper([Store Name]) ~= Upper("${selection}") But it is hard to find or search for a value in a long dropdown list, so with a little tweak on the text area html and a script, we can use filters to help get the value This code enhances a dropdown list with a search filter. HTML sets up the structure, JavaScript manages interactions (show/hide filter on button clicks and updates input on selection), and CSS controls visibility. This allows easier searching and selection in long dropdown lists. html <div id="myInput" > <span class="ddown"><SpotfireControl id="dropdown list property control" /> <span class='srchBtn'>⋯</span> </span> <span class="sfFltr hidden"><SpotfireControl id="filter item" /> <span class='closeBtn'>✕</span> </span> <div class="sfCalcVal hidden"><SpotfireControl id="label with First([Store Name])" /></div> <div class="sfInput hidden"><SpotfireControl id="hidden input field prop:'selection' /></div> </div> JavaScript //script params target = "myInput" //node elements container = document.getElementById(target); dropdown = document.querySelector(".ddown"); filter = document.querySelector(".sfFltr"); searchButton= document.querySelector(".srchBtn"); closeButton = document.querySelector(".closeBtn"); selection = document.querySelector(".sfCalcVal"); sfInput = document.querySelector(".sfInput input"); //events searchButton.addEventListener("click",()=>{ dropdown.classList.add("hidden") filter.classList.replace("hidden","visible") oldValue = sfInput.value }) closeButton.addEventListener("click",()=>{ dropdown.classList.remove("hidden") filter.classList.replace("visible","hidden") sfInput.focus(); sfInput.blur(); }) //monitor selection observer = new MutationObserver((x)=>{ sfInput.value = selection.innerText; closeButton.click(); }) observer.observe(selection, {childList: true,subtree: true}); //apply styling and attributes css = `<style> .closeBtn, .srchBtn, .resetBtn, .okBtn{ vertical-align:top; cursor:pointer; } .visible{ position:fixed; z-index:1; } .hidden{ position:fixed; z-Index:-1; } </style>` container.insertAdjacentHTML('afterend',css) The same script can be use even if we change the Property Control to a regular input But if we want more filters with additional functionality with buttons to cancel, apply or reset filters we need to tweak the code html <div class="sfInput hidden"><SpotfireControl id="input field" /></div> <div class="sfCalcVal hidden"><SpotfireControl id="First (StoreName) Calculated value" /></div> <div id="myInput" > <span class="ddown"><SpotfireControl id="Input field property: selection" /> <span class='srchBtn'>⋯</span> </span> <span class="sfFltr hidden"> <SpotfireControl id="region filter" /> <SpotfireControl id="state filter" /> <SpotfireControl id="city filter" /> <SpotfireControl id="store name filter" /> <span title="cancel" class='closeBtn'>✕</span> <span title="cancel" class='okBtn'>✓</span> <span title="reset" class='resetBtn'><SpotfireControl id="reset all filters action control" /> </span> </span> </div> JavaScript //node elements container = document.querySelector("#myInput"); dropdown = document.querySelector(".ddown"); filter = document.querySelector(".sfFltr"); searchButton= document.querySelector(".srchBtn"); closeButton = document.querySelector(".closeBtn"); okButton = document.querySelector(".okBtn"); selection = document.querySelector(".sfCalcVal"); sfInput = document.querySelector(".sfInput input"); oldValue = null //events searchButton.addEventListener("click",()=>{ dropdown.classList.add("hidden") filter.classList.replace("hidden","visible") oldValue = sfInput.value }) closeButton.addEventListener("click",()=>{ dropdown.classList.remove("hidden") filter.classList.replace("visible","hidden") sfInput.value = oldValue sfInput.focus(); sfInput.blur(); }) okButton.addEventListener("click",()=>{ dropdown.classList.remove("hidden") filter.classList.replace("visible","hidden") }) //monitor selection observer = new MutationObserver((x)=>{ sfInput.value = selection.innerText; sfInput.focus(); sfInput.blur(); }) observer.observe(selection, {childList: true,subtree: true}); //apply styling and attributes css = `<style> .closeBtn, .srchBtn, .resetBtn, .okBtn{ vertical-align:top; cursor:pointer; } .visible{ position:fixed; z-index:1; } .hidden{ position:fixed; z-Index:-1; } </style>` container.insertAdjacentHTML('afterend',css) The Action control for the icon to reset filters is: filteringSchemeName= "filtersForPropertyControls" dataFilteringSelectionCollection = Document.Data.Filterings[filteringSchemeName] filteringScheme = Document.FilteringSchemes[dataFilteringSelectionCollection] filteringScheme.ResetAllFilters() If we need
  9. Hello @Vincent Grollemund, I understand the use case. The goal is to dynamically generate sliders in Spotfire based on a list of document property names. These sliders will control the values of corresponding document properties. The approach is to generate the range sliders in html and pass the values to a single Spotfire Input control that will further parse these values into the corresponding document properties. So imagine that you have an array of html5 sliders that updates a single Spotfire input field property control (that can be hidden) The values of this input field is a CSV set of values that can later be parsed to the rest of the document properties. You will need to: Create an array of sliders based on a list of csv value pair set of document property names: dp1:1, dp2:3, dp4:2000, etc. When any of the html5 range sliders change, they update this single Spotfire Input field Property Control with a CSV set of values. e.g. dp1:44, dp2:22, dp3:12.2 etc. When this single Property Control changes, it runs an Iron Python script that parses the values to each document property Here is a POC. Since I don't know how to create Property Controls in text areas, I created a hidden template page containing a text area that holds the sliders.js and the required input field. This text area is added to the Foreword page generated by the script. The template contains the JavaScript that parses the JSON into range sliders and the range sliders updates a dpValues doc prop that holds the modified JSON string that is later parsed as document properties when its value changes. The template requires the sliders.js and the multiple line Input field. The label and other elements are optional. The JavaScript takes a second to run. The template is copied to staDynBenignLab-Jose.dxp
  10. By the way, if you want to add icons, there are many icon libraries out there. One popular one is font awesome. All you have to do is include this in your text area and start using the icons as per the documentation html <span id='fa-placeholder'></span> <h1> <i class="fas fa-home"></i> <!-- Home icon --> <i class="fas fa-user"></i> <!-- User icon --> <i class="fas fa-cog"></i> <!-- Settings (cog) icon --> <i class="fas fa-search"></i> <!-- Search icon --> <i class="fas fa-heart"></i> <!-- Heart icon --> <i class="fas fa-shopping-cart"></i> <!-- Shopping cart icon --> <i class="fas fa-bell"></i> <!-- Bell (notification) icon --> <i class="fas fa-envelope"></i> <!-- Envelope (email) icon --> <i class="fas fa-phone"></i> <!-- Phone icon --> <i class="fas fa-camera"></i> <!-- Camera icon --> <i class="fas fa-trash"></i> <!-- Trash (delete) icon --> <i class="fas fa-edit"></i> <!-- Edit (pencil) icon --> <i class="fas fa-check"></i> <!-- Check (tick) icon --> <i class="fas fa-times"></i> <!-- Times (close) icon --> <i class="fas fa-info-circle"></i> <!-- Info circle icon --> <i class="fas fa-exclamation-triangle"></i> <!-- Exclamation triangle (warning) icon --> <i class="fas fa-lock"></i> <!-- Lock icon --> <i class="fas fa-unlock"></i> <!-- Unlock icon --> <i class="fas fa-download"></i> <!-- Download icon --> <i class="fas fa-upload"></i> <!-- Upload icon --> </h1> js const placeholder = document.getElementById('fa-placeholder'); fa_link = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">` placeholder.innerHTML = fa_link
  11. @apreble, You can play with the attributes. Here is a trick you can use to explore the settings. You will need to enable developer tools from Tools > Development > Developer tools.... If you don't see that option, go to Tools > Options > Application and enable Developer tools at the bottom. You will need to close and open the application. A shortcut to show the developer tools is to press ctrl+alt+shift+F12 even if you don't see the developer tools option from the tools menu. This is the same developer menu you find in Google Chrome when clicking F12 Once you are happy with the changes, you need to copy them in your html markup
  12. Hello @apreble I believe you are referring to this article in which it mentions briefly about sending emails. The mailto value on your href attribute for you anchor tag. No JavaScript needed: <a href="mailto:your-email@example.com?subject=Question%20about%20Spotfire%20Guide&body=Hi,%20I%20have%20a%20question%20about%20this%20Spotfire%20guide:"> <span style="display: inline-block; padding: 10px 20px; background-color: #007BFF; color: white; border-radius: 5px; text-decoration: none; cursor: pointer; font-family: Arial, sans-serif; font-size: 16px;"> ☰📧 Email Me</span> </a> This is standard html and there are many great guides for basic web technologies from beginners to advanced in the Internet. The trick relies is: <a href="mailto:your-email@example.com?subject=Question%20about%20Spotfire%20Guide&body=Hi,%20I%20have%20a%20question%20about%20this%20Spotfire%20guide:"> the <a> tag defines a hyperlink, which is used to link from one page to another. The `href` attribute specifies the URL of the page the link goes to. In this case, it uses the `mailto` protocol to open the default email client. mailto: is a protocol that tells the browser to open the default email client to send an email. Email Address: `your-email@example.com`: This is the recipient's email address. Replace this with the actual email address you want the email to be sent to Query Parameters: ?subject=Question%20about%20Spotfire%20Guide&body=Hi,%20I%20have%20a%20question%20about%20this%20Spotfire%20guide: ? Indicates the start of query parameters. subject=Question%20about%20Spotfire%20Guide`: Sets the subject of the email. Spaces are encoded as `%20 &: Separates multiple query parameters. body=Hi,%20I%20have%20a%20question%20about%20this%20Spotfire%20guide Sets the body of the email. Spaces and special characters that are URL-encoded. the span has a style attribute that controls the look and feel of the rendered button
  13. Here is an interesting use case when the zoom sliders have different scales or a mix of categorical and continuous measures with 3 different approaches
×
×
  • Create New...