[{"content":" Another day, another tester leaking something they shouldn\u0026rsquo;t. You have no chance navigating your way to the flag though: It is simply not visible for you!\nDifficulty: Medium\nLet\u0026rsquo;s solve the No Accessible Vector challenge using the principles we\u0026rsquo;ve just learned. Make sure you read the Capture the Flag tips first, as this article leans heavily on the first.\nFirst, we read the description and realize that a tester built a page that we don\u0026rsquo;t have access to. We can use our first principle of using developer mistakes to theorize about the solution. One common mistake is a developer not realizing that all pages are accessible regardless of access rules. All we have to do is figure out the name of the page.\nGoing back to our client − server knowledge, there might be some data exchanged about available pages. And indeed, one of the requests contains some metadata about the page that includes pages not accessible to the current userrole:\nNow that we know the name of the page, all we have to do is open it. Attempting to open the page using the Ciphix DevTools is not possible because of the new strict mode enabled on this app. However, we know that the app can open pages and using our knowledge about the client − server protocol, it needs to have a method to do so. We\u0026rsquo;ll we have to do refer to our Client API knowledge to find available methods.\nWe can find a method called OpenForm in the Dojo documentation:\nmx.ui.openForm(\u0026#34;Portal/DevTools.page.xml\u0026#34;, { location: \u0026#34;popup\u0026#34;, callback: function(form) { console.log(form.id); } }); Unfortunately for us attempting to execute this JavaScript function leads to an error. Apparently, OpenForm does not exist in the React client anymore. We\u0026rsquo;ll have to find another way!\nSearching through the requests in Burp, we find a reference to an undocumented function OpenForm2:\nBeing undocumented provides some difficulty in figuring out which parameters the function expects. Since this is a client-side JavaScript function we can just enable a debugger on the JavaScript code and reuse the parameters from that request.\nUsing these parameters, we can construct our own JavaScript function by replacing some content with our own and executing it from the browser console:\nlet e = \u0026#34;Portal/DevTools.page.xml\u0026#34;; //PageTitle let n = {}; // ? let r = null; // ? let i = { \u0026#34;place\u0026#34;: \u0026#34;content\u0026#34;, \u0026#34;listeners\u0026#34;: {}, \u0026#34;suspended\u0026#34;: false, \u0026#34;title\u0026#34;: \u0026#34;Homepage\u0026#34;, \u0026#34;historyId\u0026#34;: \u0026#34;historyId_tks_0\u0026#34;, \u0026#34;storeBackend\u0026#34;: { \u0026#34;recordGroups\u0026#34;: {}, \u0026#34;mirrorCount\u0026#34;: {}, \u0026#34;usedSlotsCount\u0026#34;: {}, \u0026#34;slotObservers\u0026#34;: {} }, \u0026#34;name\u0026#34;: \u0026#34;Portal.Home_Web\u0026#34; } let s = { \u0026#34;name\u0026#34;: \u0026#34;Portal/MyDoctors.page.xml\u0026#34;, \u0026#34;location\u0026#34;: \u0026#34;content\u0026#34;, \u0026#34;allowedRoles\u0026#34;: [ \u0026#34;Administrator\u0026#34;, \u0026#34;User\u0026#34; ] } let c = 0; // Pages to close mx.ui.openForm2(e,n,r,i,s,c); This leads us to the hidden page and our flag. I hope this illustrates how each principle comes into play to solve these puzzles!\n","permalink":"https://maismaus.github.io/blog/capture-the-flag-solutions-no-accessible-vector/","summary":"\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003eAnother day, another tester leaking something they shouldn\u0026rsquo;t. You have no chance navigating your way to the flag though: It is simply not visible for you!\u003c/em\u003e\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e\u003cstrong\u003eDifficulty: Medium\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s solve the No Accessible Vector challenge using the principles we\u0026rsquo;ve just learned. Make sure you read the \u003ca href=\"/blog/capture-the-flag-tips\"\u003eCapture the Flag tips\u003c/a\u003e first, as this article leans heavily on the first.\u003c/p\u003e\n\u003cp\u003eFirst, we read the description and realize that a tester built a page that we don\u0026rsquo;t have access to. We can use our first principle of \u003cstrong\u003eusing developer mistakes\u003c/strong\u003e to theorize about the solution. One common mistake is a developer not realizing that all pages are accessible regardless of access rules. All we have to do is figure out the name of the page.\u003c/p\u003e","title":"No Accessible Vector (CTF solution)"},{"content":"After winning this year\u0026rsquo;s Mendix Capture the Flag event, many people have asked me for tips. I wrote this writeup with my insights for next Capture the Flag events. Read on to learn more!\nRecap The Capture the Flag event consisted of three difficulty levels: Pizza Mario (beginner–medium), Patient Portal (hard–insane) and Magic level challenges. In total, you had about 30 hours to complete the 26 challenges. Since we solved 24 challenges (and also ate and slept), this leaves about 20 hours of hacking, which means solving approximately one challenge every hour of hacking. Time is of the essence, and working efficiently is important.\nStrategy The first thing our team did after the kickoff is talk strategy. There is no single \u0026ldquo;ultimate\u0026rdquo; strategy that you should follow, but I think having a strategy defined in your team helps you to focus and work efficiently.\nIn our case, we decided to start with the Patient Portal challenges to crack some difficult cases first. We could then do the easier Pizza Mario challenges the next day when we were expecting to be more tired. We left the Magic challenges for last since they are usually not time-efficient to solve.\nIn previous years we even used a Trello board to track each challenge, but since our team was smaller this year, we didn\u0026rsquo;t feel the need for that this time around.\nWe then mostly worked on challenges individually, but always communicated which challenge we were working on, so as not to do double work.\nTechnical requirements Of course, there are some requirements before you can get started. These are my personal recommendations:\nBurp Suite – Required for analyzing and manipulating browser traffic. Ciphix DevTools – The tool provides nothing that you cannot do through JavaScript, but it makes it much easier. Make sure you have a version that works on all Mendix apps. Because of the strict mode in Patient Portal, I hardly used it this year. VS Code – Better editing, built-in error checking, and easy script/data management compared to the browser console. Slack – Useful for communicating with your team in a separate channel. When you want to switch to another challenge, make sure you update your team with your latest findings. Mendix Studio Pro – Installing the same Mendix version eliminates the possibility of differences between platform versions. Rebuilding the challenge app yourself (especially the domain model) can help you visualize the problem. Excel – I use Excel to track objects that I found, such as guids or other interesting data points. Hacking tips Understand developer mistakes Most challenges emulate a real-world scenario, including common mistakes. For any challenge:\nRead the description. Realize what the developer tried to do. Try to figure out what they actually did. Realize what common mistakes are when modelling these requirements. Exploit those mistakes. Understand web security principles The hacking challenges provided during the CTF are not unique. Most are either part of The S-Unit Top 10 or OWASP Top Ten application security risks. Make sure you understand these and are able to find and exploit them. SQL injection, cryptographic failures and cross-site scripting (XSS) are all vulnerabilities that played a part in this or previous CTF events.\nLuckily, Mendix covers most of the security issues in our day to day work. The features available in Mendix don\u0026rsquo;t contain any security issues by default, unless a developer made mistakes in its implementation. That\u0026rsquo;s why you should always be on the lookout for any custom built methods, which can re-introduce common vulnerabilities.\nUnderstand client – server communication Mendix apps are web-based applications. They are rendered in a browser, and the webapp communicates with the server. Understanding this principle is essential.\nHTTP is stateless:\nEVERY resource that is accessed via HTTP is a single request with no threaded connection between them. If you load a web page with an HTML file that within it contains three tags hitting the same server, there will be four TCP connections negotiated and opened, four data transfers, four connections closed. There is simply no state kept at the server at the protocol level that will have the server know anything about you as you come in.\nWhat does this mean in the context of a CTF? This means that the client and server must communicate expectations. When clicking a button, the client already knows the request it needs to send to the server in order to execute the buttons functionality.\nConversely, when a server receives a request, it has no context about the request. That\u0026rsquo;s why you send a session ID for authentication and the full context of what you are trying to do in the request body. The server then blindly executes your request, but it doesn\u0026rsquo;t know anything about its context except what is supplied in the HTTP request.\nAll this metadata can be analyzed (and altered) in Burp Suite.\nSo when approaching any hacking challenge, make sure you understand that what you see in the browser is just the tip of the iceberg, and the underlying data provides a treasure trove of information. Understanding the inner workings of the Mendix client is essential for hacking it.\nUnderstand the JavaScript API Once you understand the client – server relationship, you can start to dive into the functions that the browser has available. Most of this is done through the Client API. You should be able to write and execute Client API JavaScript.\nIt also helps to have a repository of common scripts, for which I use my own Docs.\nUnderstand cryptography fundamentals Many security challenges involve an encoding, encryption or signatures of some sort. All of these were used in one or more challenges this year. You don\u0026rsquo;t need to be an expert in these, but understanding how they work and their flaws might just lead you to a flag.\nIf you want to practice with these, you can use Hack This Site which offers traditional coding hacking challenges.\nConclusion With this blogpost, I hope to give some insight into the fundamentals that go into solving the CTF challenges. I hope this helps you prepare for next year. Until then!\n","permalink":"https://maismaus.github.io/blog/capture-the-flag-tips/","summary":"\u003cp\u003eAfter winning \u003ca href=\"https://www.linkedin.com/feed/update/urn:li:activity:7383427955881824257/\"\u003ethis year\u0026rsquo;s\u003c/a\u003e Mendix Capture the Flag event, many people have asked me for tips. I wrote this writeup with my insights for next Capture the Flag events. Read on to learn more!\u003c/p\u003e\n\u003ch2 id=\"recap\"\u003eRecap\u003c/h2\u003e\n\u003cp\u003eThe Capture the Flag event consisted of three difficulty levels: Pizza Mario (beginner–medium), Patient Portal (hard–insane) and Magic level challenges. In total, you had about 30 hours to complete the 26 challenges. Since we solved 24 challenges (and also ate and slept), this leaves about 20 hours of hacking, which means solving approximately one challenge every hour of hacking. Time is of the essence, and working efficiently is important.\u003c/p\u003e","title":"Tips for the next Mendix Capture the Flag event"},{"content":"When you’re developing with Mendix, every second spent waiting for your app to boot quickly adds up. But which hardware actually delivers the best performance? And how much does it matter? To answer these questions, I created a custom Mendix benchmark test and asked developers across the community to run it on their own machines. With a wide range of hardware tested, we can now draw real, data-driven conclusions.\nContinue reading on to find out how to pick the best hardware for Mendix development. Want to read the full research and methodology? Check out the deep dive here.\nBenchmark results There is a huge difference in performance across hardware configurations.\nThe test was divided into two benchmarks:\nBenchmark 1 measured the time to start a Mendix app Benchmark 2 measured runtime performance, such as calculations, database retrievals, and commits. The fastest machine completed Benchmark 1 in 00:46, while the slowest took 05:48. That means that you might have to wait seven times as long for your project to finish starting up when using improper hardware.\nRunning your project ten times a day on slower hardware can cost you nearly an hour of lost productivity every day, just waiting for your project to start.\nWe see similar discrepancies in Benchmark 2. The best score clocks in at 0:22, while the worst scores 1:56.\nHow to choose hardware This loss in productivity using a slow machine is both annoying and costly. To prevent this, it’s important to choose the correct hardware for your day-to-day work.\nIn this guide, I’ll help you find the machine best suited to your needs.\nThe first decision is to pick a platform: Windows laptop, PC, or Mac.\nWindows laptop Windows laptops are most commonly used but have the greatest variance in performance. There are large differences between good and bad Windows laptops.\nThere are good Windows laptops, but there are also a lot of mediocre ones. Make sure to pick a decent laptop using the advice in the next chapter.\nWindows desktop PC Windows PCs are easy to configure and upgrade, offer probably the best price-to-performance ratio, but sacrifice portability.\nApple Mac MacBooks score consistently good across the board. Though you will need to use Parallels because Studio Pro for Mac is still in Beta (including the extra costs this brings).\nChoosing your specs Whichever platform you choose, make sure you have at least 16GB of RAM and an SSD as the main storage device. When searching for a laptop, add any optional requirements such as screen technology and resolution, storage capacity, size, and weight.\nThen, find the best processor within your budget. Find a CPU that scores at least a 20K on PassMark in the CPU Mark column, ideally 25K+. The higher the better.\nWhen purchasing a MacBook, as long as it contains Apple Silicon (a processor such as M1, M2, M3, etc.), you are all set. You might need more RAM than a Windows laptop because you’ll need to run two operating systems at the same time. So get at least 24GB of RAM.\nSummary The CPU (processor) is the most important component for measuring Mendix performance. We can summarize our guidelines as follows:\nPick a platform. Find the best processor that you can get. Make sure you have at least 16GB of RAM (24GB for Mac). You do not need a dedicated GPU. If you have any suggestions, feel free to leave your thoughts in the comment section!\nDisclaimer: This research was conducted independently and reflects my personal methodology and findings. Performance results may vary based on many factors such as Mendix project complexity, software versions, and system configurations. I am not affiliated with Mendix or any hardware vendor, and I do not take responsibility for purchasing decisions based solely on this article. Always consider your own development needs and test accordingly before committing to any hardware.\n","permalink":"https://maismaus.github.io/blog/the-ultimate-mendix-hardware-purchasing-guide/","summary":"\u003ch3 id=\"when-youre-developing-with-mendix-every-second-spent-waiting-for-your-app-to-boot-quickly-adds-up-but-which-hardware-actually-delivers-the-best-performance-and-how-much-does-it-matter\"\u003eWhen you’re developing with Mendix, every second spent waiting for your app to boot quickly adds up. But which hardware actually delivers the best performance? And how much does it matter?\u003c/h3\u003e\n\u003cp\u003eTo answer these questions, I created a custom Mendix benchmark test and asked developers across the community to run it on their own machines. With a wide range of hardware tested, we can now draw real, data-driven conclusions.\u003c/p\u003e\n\u003cp\u003eContinue reading on to find out how to pick the best hardware for Mendix development. Want to read the full research and methodology? Check out the deep dive \u003ca href=\"https://medium.com/@mariusvanderknaap/the-mendix-benchmark-analysis-ef1d58e86b4b\"\u003ehere\u003c/a\u003e.\u003c/p\u003e","title":"The Ultimate Mendix Hardware Purchasing Guide"},{"content":" This is an addition to the Mendix hardware survey article. For full transparency, I put the research results of the benchmark analysis in this blogpost. So anyone can double check my findings. As I don’t expect many people to read this, I’m not formatting this perfectly as I would a more public blogpost. Feel free to add your thoughts!\nA note on limitations The most powerful hardware often combines different aspects to achieve its results, so it is difficult to make conclusions on single components. If a PC performs well but has both a powerful CPU and GPU, it is hard to conclude which component contributes to its success.\nI singled out some components in my own PC, swapping components to single out differences. But n=1.\nOverall the sample size for this research is quite small. While trends are visible, it is hard to draw definitive conclusions on anything. In addition, this is not my area of expertise at all, so whilst I did my best to perform a proper analysis, it’s likely I made mistakes or “scientific faux pas” along the way.\nThat being said, I feel some trends are definitively visible, and the main conclusion holds up: there is a huge difference in performance across devices, so picking the right hardware matters.\nCPU MX Benchmark vs PassMark scores I am interested whether a PassMark score correlates with the benchmark results. I looked up every CPU on PassMark and added the score to the Excel. Then, we can plot the Benchmark vs PassMark scores:\nBenchmark scores (Y-axis) vs. PassMark scores (Y-axis)\nWe can definitely see a correlation, though not linear. Improvements seem to cap around a 20K PassMark score. Also, the highest PassMark score (45,478 for AMD Ryzen 9 5950X with a Benchmark2 score of 32316) is about 20% slower than a CPU that scores much lower on PassMark (34271 for AMD Ryzen 7 7800X3D with a Benchmark2 score of 27098).\nCores vs. singlethreaded performance Next, it would be interesting to see what contributes more towards performance: more cores or better singlethreaded performance?\nBenchmark scores vs. number of cores\nBenchmark scores vs. singlethreaded score by PassMark\nWe see a similar trend as for the PassMark overall scores: more cores/better singlethreaded performance is a better benchmark score.\nRaw data for CPU analysis\nRAM For RAM, we can explore RAM speed and amount.\nRAM amount in GBs vs. Benchmark scores\nRAM speed vs. Benchmark scores\nAlthough the results trend slightly downwards, RAM amount does not seem to impact performance much. The best results are already achieved in the bottom left of the chart, even though that means a low amount of RAM and a high benchmark result. Either way, I always recommend getting at least 16GB of RAM nowadays.\nRAM speed does seem to affect performance, but it is likely that lower RAM speeds are indicitative of an older system lacking in other areas.\nGraphics card Graphics cards show very little activity in Task Manager while running the benchmarks. Graphics card are spread across the board. For example, the Qualcomm Adreno X1–85 GPU has a PassMark score of just 2865 compared to the AMD RADEON RX 7900 XTX GAMING with a PassMark score of 31113, with comparable and both good benchmark scores.\nTherefore, I conclude that graphics card do not impact Mendix Studio Pro performance.\nGraphics card PassMark vs. Benchmark scores\nStorage For storage, you could measure benchmark scores vs read and write speeds. While initially I suspected this might have a great impact on performance since Mendix has to write the project to the deployment folder, initial testing already showed very little activity in Task Manager while performing the benchmarks.\nThe benchmark results tell a similar story.\nHard drives have slow read/write speeds, but are present in both high and low scoring benchmarks. SSDs have fast read/write speeds but are still present in the bottom of the list of benchmark scores. There is still an NVMe SSD (the fastest read/write speeds) in the middle of the pack. Therefore, I conclude that the storage device has little impact on performance.\nOperating system Which type of hardware typically performs the best? I differentiate four kinds:\nWindows PC Windows laptop MacBook MacBook using Parallels Plotting all outcomes, we can calculate the average result for each type of hardware. Also interesting is plotting the average deviation, so how much variance is there in that hardware type. For this, I used the AVEDEV function.\nAverages per hardware type\nWe find that PCs score the best in Benchmark 1, while Parallels performs the best in Benchmark 2. PCs also have the smallest deviation for both tests. Windows laptops score the worst and have by far the biggest deviation in scores.\nSince there were only a handful of powerful PCs tested, it makes sense they win in most scenarios.\nRaw data for hardware type\nBenchmark 1 vs Benchmark 2 If the system performs well in Benchmark 1, will it also perform well in Benchmark 2? In other words, is performance consistent across different measurements?\nTo test this, we can test the correlation between the two scores. First, I normalized the durations to be both in ms, then calculated the correlation coefficient between the two scores using the function =CORREL(B2:B32;C2:C32). The correlation coefficient is 0.905, indicating a very strong relation between the two outcomes.\nCorrelation coefficient between benchmark 1 \u0026amp; 2\nPrice vs Performance This would be an interesting topic, were it not too complicated. I decided to leave price-performance out of the analysis.\nPrices vary wildly across regions, and Mendix is a global product. MSRP does not mean the price you can actually acquire the hardware for. The launch price is not the price you always pay for it, and products can become cheaper over time (but not always). ","permalink":"https://maismaus.github.io/blog/the-mendix-benchmark-analysis/","summary":"\u003cblockquote\u003e\n\u003cp\u003eThis is an addition to the \u003ca href=\"https://medium.com/mendix/the-ultimate-mendix-hardware-purchasing-guide-987da31b65e5\"\u003eMendix hardware survey\u003c/a\u003e article. For full transparency, I put the research results of the benchmark analysis in this blogpost. So anyone can double check my findings. As I don’t expect many people to read this, I’m not formatting this perfectly as I would a more public blogpost. Feel free to add your thoughts!\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"a-note-on-limitations\"\u003eA note on limitations\u003c/h2\u003e\n\u003cp\u003eThe most powerful hardware often combines different aspects to achieve its results, so it is difficult to make conclusions on single components. If a PC performs well but has both a powerful CPU and GPU, it is hard to conclude which component contributes to its success.\u003c/p\u003e","title":"The Mendix benchmark analysis"},{"content":"The Dojo era For over a decade, Mendix used Dojo as a front-end client framework. Frameworks like Dojo save developers a lot of time by providing useful libraries out of the box.\nDojo was among the most popular JavaScript frameworks for many years. However, the JavaScript landscape evolves rapidly, and other libraries have surpassed Dojo by being faster, more comprehensive, and easier to use. One of these libraries is React, which quickly became one of the most popular JavaScript frameworks.\nMendix transitioned to using React and the Pluggable Widgets API as a preferred method for building custom widgets and steadily phased out Dojo widgets.\nThe main web client, however, is still based on Dojo. This eventually has to be replaced to move to a full React client.\nThe Mendix React Client The React client is available from Mendix 10.7 and above. The React client offers several benefits:\nIt is faster It allows incremental loading It is future-proof Before you can migrate to the React client, you cannot have any Dojo widgets in your project.\nWhile migrating to React is not a necessity yet and won’t be for a while, knowing about its existence helps you build future-proof applications. Avoiding Dojo widgets now saves you from having to migrate them a year or two from now.\nSo, what can you do? There are some default Mendix widgets based on Dojo. These should be avoided, and their modern counterparts should be used instead.\nData Grid 1 ➡ DataGrid2 Reference Selectors \u0026amp; Drop-down ➡ Combo box Dynamic \u0026amp; Static Image ➡ Image Template Grid ➡ Gallery If your first thought is “…but Data Grid 2”, give the latest version another try. Mendix worked hard on these widgets and I can confidently say that they are now much better than their legacy counterparts.\nFurthermore, make sure to follow these guidelines:\nWhenever you add a new custom widget, make sure it is based on React. Enable Migration mode to check existing widget compatibility. For more information, read up on the Mendix React Client documentation. Final thoughts While you don’t have to migrate all your existing widgets just yet, you shouldn’t introduce deprecated widgets to your projects anymore. This ensures that you build future-proof applications and prevents you from a lot of work in the future. Your future self will thank you!\n","permalink":"https://maismaus.github.io/blog/the-mendix-react-client-is-coming/","summary":"\u003ch2 id=\"the-dojo-era\"\u003eThe Dojo era\u003c/h2\u003e\n\u003cp\u003eFor over a decade, Mendix used Dojo as a front-end client framework. Frameworks like Dojo save developers a lot of time by providing useful libraries out of the box.\u003c/p\u003e\n\u003cp\u003eDojo was among the most popular JavaScript frameworks for many years. However, the JavaScript landscape evolves rapidly, and other libraries have surpassed Dojo by being faster, more comprehensive, and easier to use. One of these libraries is React, which quickly became one of the most popular JavaScript frameworks.\u003c/p\u003e","title":"The Mendix React Client Is Coming: Here’s What You Need to Know"},{"content":"Have you ever wondered how much of a difference in performance a better laptop makes? Have you ever wondered what aspects of a laptop you should focus on when looking for the best development machine for building Mendix applications?\nIntroducing the Mendix Hardware Survey! During this survey, I want to research what contributes to a powerful Mendix development machine. The goal is to find out where to spend your money when looking for a new laptop.\nThis research consists of two tests.\nBenchmark 1 measures how long it takes to boot up a Mendix app. Benchmark 2 measures how fast common runtime operations complete. The more hardware configurations perform these tests, the better the results will be, so this is where you come in. The results will be shared in an upcoming hardware purchasing guide for Mendix developers.\nHow to participate Preparation Enter your details in columns A - I of the data sheet. Download the .mpk package. Extract the package and open the .mpr file in Mendix 10.20. Make sure there is no other Mendix app or resource heavy software running. Running the benchmarks Run the project locally:\nStart a timer when pressing the run button. Stop the timer when the \u0026ldquo;Your app is running!\u0026rdquo; message appears. Write down the times in column N - R under Benchmark 1. Press the Run benchmark button and wait for it to finish (approx. 5 mins).\nWrite down your results in column S - W under Benchmark 2.\nRepeat the benchmarks up to 5 times. Especially benchmark #1 (boot time) is important to do at least three times. Completely stop the app and rerun to perform the test again. No need to clean deployment directory in between. You can also perform multiple benchmarks in different configurations to compare the results (Laptop on battery vs. plugged in, enabling high performance mode etc.).\nIf you prefer to stay anonymous, you can download a copy of the sheet and mail it to me separately at mendixhardwaresurvey.visible078@passfwd.com.\nThank you and please find me on Slack when you have any questions!\n","permalink":"https://maismaus.github.io/blog/the-mendix-hardware-survey/","summary":"\u003cp\u003eHave you ever wondered how much of a difference in performance a better laptop makes? Have you ever wondered what aspects of a laptop you should focus on when looking for the best development machine for building Mendix applications?\u003c/p\u003e\n\u003cp\u003eIntroducing the \u003cstrong\u003eMendix Hardware Survey\u003c/strong\u003e! During this survey, I want to research what contributes to a powerful Mendix development machine. The goal is to find out where to spend your money when looking for a new laptop.\u003c/p\u003e","title":"The Mendix hardware survey"},{"content":"For the last decade, Mendix has relied on SVN for version control. However, with Git surpassing SVN in functionality, migrating to Git became inevitable. With all the talk about using Git with Mendix, you probably considered migrating or have done so already.\nHaving relied on SVN for so long, many developers became accustomed to working with SVN. Now that updating is suddenly called pulling, and committing doesn’t mean what it used to mean anymore, many developers are confused about the differences between SVN and Git.\nI dove into the inner workings of Git, and in this post, I’ll explain what has changed and what it means for you as a Mendix developer.\nWhat is Git? To establish a common understanding of Git, let’s delve into some key concepts.\nGit is an open-source framework for version control. Don’t confuse it with GitHub which is the most commonly used implementation of the Git version control system currently owned by Microsoft. Mendix uses Git, and you can host it on the Team Server or your own Git provider.\nGit is a decentralized version management system while SVN is centralized. This concept is important because it changes many of the concepts associated with version management. In SVN, the repository on the Team Server is always the single source of truth. In Git, each repository is a complete copy of itself.\nA repository contains your Mendix project including its history. Creating a local copy of the code on your machine is called cloning. You refer to the repository on the cloud as the remote. When you commit, you save the changes to your local repository. Pushing sends these changes to the remote. If the remote has commits that you don’t have locally, the remote is ahead of you. Before you can push, you have to merge the remote changes into your local repository first. Merging is the process of combining changes from different development lines.\nIn SVN, you had to update your local copy before you could commit. In Git, this works the other way around: you always commit your work before pulling. Using these newly learned concepts, we can understand why. In SVN, you have to update your local copy to be in sync with the single source of truth before you can send an update to the server. Merging the remote changes is needed before the server is ready to receive an update. In Git, you apply an update to your local copy and then pull incoming changes. These incoming changes are merged into your local repository. This is good: it separates your work from the changes required to merge. In Git, you’ll commit, then pull, merge, then push.\nMerging in Git While you probably know merging from SVN as moving changes from one branch to another, it refers to a bit more than that. Merging is the process of combining changes from one line of development into another. This could also mean merging changes from the remote into your local repository on the same branch.\nGit offers different merge strategies such as fast-forwarding, cherry-picking, and rebasing. In Mendix 9, you’ll always use a basic merge, where you pull changes from the remote into your local repository and commit the merged changes. Because this method was confusing to many, Mendix introduced the rebase feature in Mendix 10.5.\nRebasing means your changes are stashed temporarily, your local copy is updated to the remote version, and then the changes are applied again. So while you developed your changes on an older commit, you’re updating your local repository and ‘acting like you’ve really developed the changes on the latest commit instead. This can only be done if there is no merge conflict between your local and the incoming changes.\nFor a deep dive into changes and conflict resolution, check out the extensive Mendix docs.\nHandling conflicts Git excels in merging text-based files. However, since Mendix projects are packaged in a binary .mpr file, it has difficulty merging two different .mpr files. That’s why Mendix uses a custom merge software on top of Git for .mpr files. This allows you to solve merge conflicts directly from Studio Pro.\nBecause of the binary .mpr file, any Git command line interactions usually result in a merge conflict. The two binary files (local and remote copy) cannot be merged by Git itself. When you perform Git commands, always open the project in Studio Pro to activate the Mendix merger.\nFor any conflicts outside of Studio Pro, you’ll need other software. You should be able to use any compatible Git software. If you like command-line interfaces you could install Git-cli. If you prefer a more visual interface, try GitHub Desktop. For more information on GitHub Desktop, check out\nKasja Maksymiak\n’s guide on setting it up. Conflicts in text files also show up automatically when you open them in Visual Studio Code.\nWhat’s next? Git is hard. Git has endless possibilities, and as Mendix developers we only use the tip of the iceberg. Git requires strict agreements within teams on the way of working. Make sure you agree on merging strategies and committing best practices internally.\nIn Git, it’s more common to work in branches than it is in SVN. Investigate whether you should use branches more often than you’re used to. As repositories are now a local copy, you won’t have to download the entire repository when creating a new branch anymore.\nBranching strategies depend largely on your project requirements. As a starting point, I suggest working on larger feature branches. Have your production branch be the main line and create a generic development branch. Then create feature or version branches from development such as develop-newfeature or develop-v2. Merge the feature branches into the develop branch regularly, then merge into the main line once you’re ready for a production deployment.\nSome final advice Familiarize yourself with Git commands. You don’t need to use them, but it helps to understand what’s happening under the hood to solve any Git issues that may occur.\nAs you can imagine, moving to Git is a huge undertaking. Mendix is working hard on improving Git. On the roadmap are visually comparing documents across revisions and pull requests. If you have ideas on improving Git in Mendix, make them heard on the idea forum.\nWhat features do you want to see in Git for Mendix?\n","permalink":"https://maismaus.github.io/blog/everything-you-need-to-know-about-git-for-mendix/","summary":"\u003cp\u003eFor the last decade, Mendix has relied on SVN for version control. However, with Git surpassing SVN in functionality, migrating to Git became inevitable. With all the talk about using Git with Mendix, you probably considered migrating or have done so already.\u003c/p\u003e\n\u003cp\u003eHaving relied on SVN for so long, many developers became accustomed to working with SVN. Now that updating is suddenly called pulling, and committing doesn’t mean what it used to mean anymore, many developers are confused about the differences between SVN and Git.\u003c/p\u003e","title":"Everything you need to know about Git for Mendix"},{"content":"Creating lists in Mendix is one of its core features. You can use lists for looping over objects. Lists can improve performance by batch-committing objects. Aggregation functions can perform calculations and summations on your list, making them an integral part of any Mendix applications. However, as lists grow larger, they start to become SLOW.\nYou might wonder why.\nMendix is based on Java. This means that most of what you create in Mendix is translated into Java code. When you create a Mendix list, you’re essentially creating an ArrayList. ArrayLists are convenient because they’re flexible. They allow you to add objects to it on the fly without requiring you to pre-allocate memory. ArrayLists support a wide range of operations, including insertion, removal, and retrieval of elements, making them versatile data structures for managing collections of objects. Of course, Mendix handles all of this in the background.\nAn ArrayList is constructed by indexing objects in the list, and they are indexed in chronological order. Retrieving objects through the index (“get the 10th object in the list”) is fast. However, searching for an entry with a specific value is slow because it has no index on list values. This means that for find operations, Mendix has to loop over each list item, and search time grows linearly with list size.\nHow a Single Trick Can Improve Performance Java offers numerous interfaces for creating collections of objects, and one of them is the Map. A Map differs from Lists in several aspects. Most importantly, a Map is a collection of Key-Value pairs. The Key serves as a unique identifier associated with a Value. Maps are indexed on Key values, making retrieval using Key lightning fast.\nWhat Does This Mean in Practice? Let’s say you’re doing a migration for a large financial institution. They have a CSV sheet containing transactions with an attribute CustomerID. You have a Mendix app with their Customers already, and you want to import the transactions and link them to a customer based on the CustomerID.\nIn traditional Mendix, this might look something like this:\nIn this example, the client has 10,000 customers and 100,000 transactions that need to be linked to a customer. Mapping using a database retrieval took 213 seconds. Alternatively, we can try retrieving the list of customers and use a find operation:\nWith the list find method, this took 75 seconds.\nWith a Map, this was done in 3.5 seconds. A 20x increase in performance! So what kind of magic is going on behind the scenes here?\nLet’s Dive into Some Code!\nWe begin by building a Map of Key-Value pairs and store it in the Context so that we have access to it during the current microflow, but gets garbage collected when the microflow is finished. The Key contains our CustomerID so we can find the Customer. The Value contains our Mendix Customer object. The Java action retrieves all Customer objects from the database and turns them into a Map.\nIContext context = getContext(); context.getData().remove(ObjectMapType); Map\u0026lt;Object, IMendixObject\u0026gt; objectMap = new HashMap\u0026lt;\u0026gt;(); List\u0026lt;IMendixObject\u0026gt; objectList = XPath.create(context, ObjectMapType).all(); for (IMendixObject mxObject : objectList) { objectMap.put(mxObject.getValue(context, ObjectKey), mxObject); } context.getData().put(ObjectMapType, objectMap); When we want to find a Customer, we can retrieve it from the Map using its Key value:\n@SuppressWarnings(\u0026#34;unchecked\u0026#34;) Map\u0026lt;Object, IMendixObject\u0026gt; objectMap = (Map\u0026lt;Object, IMendixObject\u0026gt;) getContext().getData().get(ObjectMapType); if (objectMap.isEmpty()) { Core.getLogger(\u0026#34;ObjectMap\u0026#34;).info(\u0026#34;No object map found for map type \u0026#34; + ObjectMapType); return null; } IMendixObject resultObject = objectMap.get(KeyValue); if(resultObject != null) { return resultObject; } Core.getLogger(\u0026#34;ObjectMap\u0026#34;).debug(\u0026#34;No object found for key \u0026#34; + KeyValue); return null; Our resulting microflow in Mendix is very similar to the find operation, with some actions replaced by our custom Java actions:\nYou can find the ready-to-use Java actions here.\nWhen (Not) to Use This Don’t use Maps when you have a list of just a few hundred objects. The performance gains only start to become noticeable when dealing with tens of thousands of objects. The larger the number of objects in your list, the greater the performance gains. This means a theoretically infinite performance enhancement! (as long as your server memory allows for storing the Maps, of course).\nPros Lightning-fast retrieval of objects. Easy to set up. Cons Additional startup time is required for setting up the Map. Less flexible than normal Lists. Java knowledge is required for extending functionality. Final Thoughts While Mendix is a low-code platform, it’s crucial to understand that ultimately, everything is still code. Realizing and embracing this fact is essential for developing advanced modeling techniques.\nThere are many further optimizations possible in the solution described here. My code stores the entire Mendix objects as values in the created map. This is useful because we can return the objects directly from the Java action but has a negative impact on performance and memory usage. In my tests, storing GUIDs instead of Mendix objects doubled the performance of the Map. However, as modeling with GUIDs is more complex, I chose to explain only the basic solution.\n","permalink":"https://maismaus.github.io/blog/mastering-mendix-maps/","summary":"\u003cp\u003eCreating lists in Mendix is one of its core features. You can use lists for looping over objects. Lists can improve performance by batch-committing objects. Aggregation functions can perform calculations and summations on your list, making them an integral part of any Mendix applications. However, as lists grow larger, they start to become \u003cstrong\u003e\u003cem\u003eSLOW\u003c/em\u003e\u003c/strong\u003e\u003cem\u003e.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eYou might wonder why.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMendix is based on Java\u003c/strong\u003e. This means that most of what you create in Mendix is translated into Java code. \u003cstrong\u003eWhen you create a Mendix list, you’re essentially creating an ArrayList\u003c/strong\u003e. ArrayLists are convenient because they’re flexible. They allow you to add objects to it on the fly without requiring you to pre-allocate memory. ArrayLists support a wide range of operations, including insertion, removal, and retrieval of elements, making them versatile data structures for managing collections of objects. Of course, Mendix handles all of this in the background.\u003c/p\u003e","title":"Mastering Mendix Maps: Unlocking Lightning-Fast Performance"},{"content":"If your app runs in the Mendix cloud you are probably using the Mendix Developer Portal to deploy your application. Deploying this way takes several steps: creating a deployment package, transferring it to the desired environment, and restarting the app. Each step can take several minutes after which you have to check back to see if you can progress further.\nYou’ll save a lot of time and frustration by automating this process. In this blog post, I’ll go over my automated deployment pipeline using Postman. The Postman collection contains several services for the Mendix API. It has services to build, transport, and deploy new packages. After setting it up, you’ll be able to use the Postman Collection Runner to run these requests in sequence and deploy with a single press of a button!\nFind the finished collection here.\nIt includes a folder for new deployments and a folder to redeploy the last package. The latter can be used to transport a package from one environment to the other, i.e. from Test to Acceptance. You can start the Collection Runner on one of the folders depending on your use case.\nStep 1: Preparing the environment Create a new Postman Collection. We use that to store our requests and variables.\nBefore getting started on requests, we need the proper credentials for the Deploy API. We’ll need tokens for both the Deploy API and the App Repository API.\nFollow the steps described in both below doc pages to set up your API credentials.\nDeploy API App Repository API Store the credentials in your Collection Variables as MendixUsername, MendixApiKey and MendixPersonalAccessToken.\nStep 2: Test the connection and retrieve the project App ID Once the connection is working, we’ll need the app’s identifiers. We can retrieve our apps using the following API call. Store the returned AppID and ProjectID as a variable.\nGET https://deploy.mendix.com/api/1/apps Mendix-Username: {{MendixUsername}} Mendix-ApiKey: {{MendixApiKey}} Store the AppID and ProjectID in Environment Variables if you work on multiple Mendix apps. Select the right environment before running to deploy different apps each time!\nLastly, store the desired branch that you want to deploy as a variable. All preparation is now done and your variables should look like this:\nStep 3: Retrieve the latest revision Once we have the App ID and Project ID stored in our global variables, we can use them to perform our API calls. The first step is to retrieve the latest revision from the App Repository API. We also need to send our App Repository API credentials in the headers.\nGET https://repository.api.mendix.com/v1/repositories/{{ProjectID}}/branches/{{branch}}/commits Authorization: MxToken {{MendixPersonalAccessToken}} Once we receive a response, we have to store part of it to reuse it in subsequent requests. I chose to store them as collection variables. To do so, we can run a test to store a collection variable after a successful response:\nconst response = pm.response.json(); const latestRevision = response.items[0].id; pm.test(\u0026quot;Has latest revision\u0026quot;, () =\u0026gt; { pm.expect(latestRevision).to.not.be.undefined; pm.collectionVariables.set(\u0026quot;latestRevision\u0026quot;, latestRevision); }); Step 4: Retrieve the current environment package The Deploy API expects a version number to be included upon package creation. The most logical approach is to retrieve the package that is currently running and add a minor version to that. In order to do so, first set an environment variable as the name of the environment you want to deploy, then execute the following request:\nGET https://deploy.mendix.com/api/1/apps/{{AppID}}/packages Mendix-Username: {{MendixUsername}} Mendix-ApiKey: {{MendixApiKey}} From here on out, you’ll need to include the authentication headers every time. Make sure to include both Mendix-Username and Mendix-ApiKey for each request!\nThen, store the version as a collection variable using a test script:\nconst response = pm.response.json(); const version = response.Version; pm.test(\u0026quot;Received current package version\u0026quot;, () =\u0026gt; { pm.expect(version).to.not.be.undefined; pm.collectionVariables.set(\u0026quot;version\u0026quot;, version); }); Step 5: Create a new package Once we have the version stored, we can start creating a new package. Before we send the request, we can run a Pre-Request script to calculate the version:\nlet version = pm.collectionVariables.get(\u0026quot;version\u0026quot;); if(version) { version.lastIndexOf(\u0026quot;.\u0026quot;); let packageVersion = version.substring(0,version.lastIndexOf(\u0026quot;.\u0026quot;)); let minorVersion = packageVersion.substring(packageVersion.lastIndexOf(\u0026quot;.\u0026quot;) + 1, version.length); let majorVersion = packageVersion.substring(0, packageVersion.lastIndexOf(\u0026quot;.\u0026quot;)); let newMinorVersion = parseInt(minorVersion) + 1; pm.collectionVariables.set(\u0026quot;newVersion\u0026quot;, majorVersion + \u0026quot;.\u0026quot; + newMinorVersion); } else { pm.collectionVariables.set(\u0026quot;newVersion\u0026quot;, \u0026quot;0.0.0\u0026quot;); } Then, we can send a request to start the package build:\nPOST https://deploy.mendix.com/api/1/apps/{{AppID}}/packages { \u0026quot;Branch\u0026quot; : \u0026quot;trunk\u0026quot;, \u0026quot;Revision\u0026quot; : \u0026quot;{{latestRevision}}\u0026quot;, \u0026quot;Version\u0026quot; : \u0026quot;{{newVersion}}\u0026quot;, \u0026quot;Description\u0026quot; : \u0026quot;New minor version deployed from Postman\u0026quot; } And store the returned package ID for later use in a test script:\nconst response = pm.response.json(); const packageID = response.PackageId; pm.test(\u0026quot;Received package ID\u0026quot;, () =\u0026gt; { pm.expect(packageID).to.not.be.undefined; pm.collectionVariables.set(\u0026quot;packageID\u0026quot;, packageID); }); Step 6: Check the package build status The package is now building asynchronously on the Mendix Cloud. In order to proceed, we must wait for the package to be done. We’ll keep sending a request every minute or so until the package is done:\nGET https://deploy.mendix.com/api/1/apps/{{AppID}}/packages/{{packageID}} We can have Postman retry the request after a minute if the test case fails:\nconst response = pm.response.json(); pm.test(\u0026quot;Build succeeded\u0026quot;, function () { pm.expect(response.Status).to.equal(\u0026quot;Succeeded\u0026quot;); }); if (response.Status !== \u0026quot;Succeeded\u0026quot;) { console.log(\u0026quot;Package is still building...\u0026quot;); if(response.Status === \u0026quot;Failed\u0026quot;) { return false; } else { setTimeout(() =\u0026gt; {}, 60000); // wait for 1 minute before retrying postman.setNextRequest(pm.info.requestId); // retry the current request } Step 7: Transport the package Once the package is done, we can transport it to the environment. Luckily this is a synchronous action so we don’t have to include any timeouts here.\nPOST https://deploy.mendix.com/api/1/apps/{{AppID}}/environments/{{environment}}/transport { \u0026quot;PackageId\u0026quot;: \u0026quot;{{packageID}}\u0026quot; } Step 8: Stop the environment The final steps are about rebooting the environment so the new package is activated.\nPOST https://deploy.mendix.com/api/1/apps/{{AppID}}/environments/{{environment}}/stop Step 9: Start the environment After stopping the environment, we can start it up again.\nPOST https://deploy.mendix.com/api/1/apps/{{AppID}}/environments/{{environment}}/start We’ll store the Job ID in the response for later use:\nconst response = pm.response.json(); pm.test(\u0026quot;Received JobId\u0026quot;, function () { let jobId = response.JobId; pm.expect(jobId).to.not.be.undefined; pm.collectionVariables.set(\u0026quot;JobID\u0026quot;, jobId); }); Step 10: Check start status (optional) The request to start the environment is asynchronous again. Optionally, we can send another request using timeouts to keep checking for the start status.\nGET https://deploy.mendix.com/api/1/apps/{{AppID}}/environments/{{environment}}/start/{{JobID}} Keep checking the status using the Postman tests:\nconst response = pm.response.json(); if (response.Status === \u0026quot;Starting\u0026quot;) { console.log(\u0026quot;Environment is starting...\u0026quot;); setTimeout(() =\u0026gt; {}, 60000); postman.setNextRequest(pm.info.requestId); } pm.test(\u0026quot;Environment has started\u0026quot;, function () { pm.expect(response.Status).to.equal(\u0026quot;Started\u0026quot;); }); Once the environment has started, the collection run will finish and your new package will be available online.\nYou can now use the Postman Collection Runner to run all the requests in order. This automatically deploys a new package from the latest revision in your selected branch. If anything needs to be fixed as expected, refer to the extensive Mendix documentation available, or leave a comment below!\nNext steps This blog post is meant to give you an idea of what is possible using the available Mendix API services. From here on out, you can expand the functionality as necessary. For my project, I have an integration with the Slack API sending messages when I start and finish a deployment to notify the team. You can send e-mails on deployments, you can integrate the service with a tool like Jenkins for a scheduled deployment. Anything is possible from here on out! I hope this blog post saves you some time in the future and perhaps inspires you to explore new automation capabilities.\n","permalink":"https://maismaus.github.io/blog/quick-and-easy-deployments-using-postman/","summary":"\u003cp\u003eIf your app runs in the Mendix cloud you are probably using the Mendix Developer Portal to deploy your application. Deploying this way takes several steps: creating a deployment package, transferring it to the desired environment, and restarting the app. Each step can take several minutes after which you have to check back to see if you can progress further.\u003c/p\u003e\n\u003cp\u003eYou’ll save a lot of time and frustration by automating this process. In this blog post, I’ll go over my automated deployment pipeline using Postman. The Postman collection contains several services for the Mendix API. It has services to build, transport, and deploy new packages. After setting it up, you’ll be able to use the Postman Collection Runner to run these requests in sequence and deploy with a single press of a button!\u003c/p\u003e","title":"Quick and Easy Deployments on Mendix Cloud Using Postman"},{"content":"No matter what you do for a living, making sure you have the right tools for the job is essential for any working professional. Mendix Studio Pro is a great piece of software to build apps with, but there are a plethora of other tools that can help you build apps more efficiently than ever before. In this article, I go over my recommendations for must-have tools for everyday use in Mendix development.\n1. Use Prettier for automatic file formatting If you have experience writing custom styling, you are probably familiar with CSS indentation formatting. Did you know there are easy ways to automate this? I use the Visual Studio Code extension Prettier to automatically format my SCSS whenever I save a file.\nYou can even customize the formatting rules on a per-project basis, using a Prettier configuration file! This way, you can ensure that everyone on your team uses the same formatting.\n2. Extend Mendix Version Control by installing TortoiseSVN Mendix uses Subversion (SVN) for version management. Most of the things you need for this are included under the Version Control menu item in Studio Pro, but you can download TortoiseSVN to get some additional functionalities.\nWith TortoiseSVN, you can:\nUpdate to a specific revision, for instance, to compare changes between commits. Rename and archive branches. Resolve merge issues that occurred outside of Studio Pro. Update and commit without having to open Studio Pro. Add items to the ignore list, preventing merge conflicts for compiled files. Make sure you check which TortoiseSVN version you need for your Studio Pro version.\n3. Enhance your browser with bookmarklets Mendix offers a JavaScript API that you can use to your advantage. Most modern browsers offer the option to save JavaScript snippets in your bookmarks called bookmarklets.\nBookmarking a JavaScript function allows you to call it with a single mouse click directly from your browser. The possibilities are endless: logging in, opening a page, executing a microflow… You can bookmark any function that you use regularly during app development.\nLog out of any Mendix application instantly by clicking a bookmark on your toolbar javascript:(function()%7Bmx.logout()%3B%7D)()%3B\nI use this bookmarklet maker to generate bookmarklets from my JavaScript snippets.\n4. Squeeze more efficiency out of Windows using PowerToys If you’ve ever used a MacBook before, I’m sure you miss the Spotlight feature after switching to Windows. Did you know Microsoft offers this — and more — with PowerToys? For instance, it adds functionality in File Explorer to preview .svg and .md files, a right-click image resizer, and a nifty color picker. A must-have for anyone using Windows on a daily basis!\n5. Automate your installation process with Chocolatey Again, if you used a MacBook in the past, you’ve probably heard of Homebrew. There is a Windows equivalent called Chocolatey. Chocolatey is a package manager for Windows that allows you to search for and install software right from the command prompt.\nTo illustrate, let me give you an example. I’m sure you are familiar with the traditional method of installing new software. It goes something like this:\nOpen browser Search for software Load the website Navigate to the download page Find the correct version Download installer Open the installer Go through the installation wizard Done With Chocolatey, you can:\nOpen command prompt Enter choco install PowerToys -y Done Sounds good? That’s because it is! Now let’s get Mendix Studio Pro available in Chocolatey by upvoting my idea on the forum.\n6. Use the built-in Windows Snipping Tool to capture parts of your screen Got a problem that you want to visualize? Use the Snipping Tool to capture parts of your screen in a screenshot. That way, you can keep your screenshots focused on the exact item that you want to share instead of sharing your entire screen. Simply press ⊞ Win + Shift + S and drag to select the part of the screen you want to capture.\n7. Improve your integration testing with Postman One of Mendix’s strengths is its ability to integrate with many other systems. Integrations are a key part of many Mendix applications. However, troubleshooting a connection from within Mendix is often a hassle because of the configuration required to set it up. You can use Postman instead to perform web service calls to and from Mendix.\nPostman also offers a collection runner, where you can configure a number of web service requests in a specific order. You can use this on top of unit tests for test automation:\nI’m working on a blogpost about automated Mendixcloud deployments from Postman. Follow me on LinkedIn to be the first to hear about it!\n8. Run a local mail server using FakeSMTP Configuring an external e-mail server to work locally can be a nuisance. You might not have an internet connection, configuration settings change or you don’t want to have to reconfigure the e-mail server every time you switch databases locally. For these scenarios, FakeSMTP is a perfect solution. This lightweight Java package will run a local e-mail server that you can connect to in seconds. Because it is written in Java, you can use it on any operating system too!\n9. Use Fiddler to analyze incoming and outgoing traffic If you want to have more insight into what data is coming in and going out, Fiddler is a must-have tool. You can route all internet traffic through Fiddler to analyze in detail what requests are being made between your computer and external servers. Make sure to configure the Fiddler port as a proxy in your Mendix project settings so all traffic is properly routed through it!\n10. Slack Did you know there is an entire Mendix community available on Slack? This active community can help you out with problems. Find the Mendix Slack community here!\n","permalink":"https://maismaus.github.io/blog/10-essential-tools-for-Mendix-development/","summary":"\u003cp\u003eNo matter what you do for a living, making sure you have the right tools for the job is essential for any working professional. Mendix Studio Pro is a great piece of software to build apps with, but there are a plethora of other tools that can help you build apps more efficiently than ever before. In this article, I go over my recommendations for must-have tools for everyday use in Mendix development.\u003c/p\u003e","title":"10 Essential Tools for Mendix App Development"},{"content":"Here’s the deal — you’re in your twenties, freshly graduated from college. Maybe you’ve flirted with coding in the past, or hacked your way through a few websites, and generally, you have an affinity with modern tech. Or perhaps you are a psych student who enjoyed working with statistics, and you’re starting to realize that you have to pay those bills eventually.\nSooner or later, you’ll need to find yourself one of those “tech jobs”.\nBefore I started working as a Mendix developer I had serious doubts about my future career path. I liked writing code, but I didn’t see myself doing that all day, every day. But I did like building things. I like looking back at a days’ work and seeing the results. That’s what building software is. I didn’t know about Low-code until I was approached by my previous employer who explained the Mendix platform to me.\nAs a Mendix developer, you build digital applications without writing traditional code. Instead, you’re building applications using prebuilt parts. You design pieces of logic called microflows, which Mendix translates into things computers can understand once you launch the app. A microflow looks like a traditional workflow diagram. It has a start and an end and executes the pieces of logic in between.\nBecause the pieces are prebuilt and ready to use, you can develop logic up to ten times faster than traditional coding methods. And because the learning curve is much less steep than coding, it’s a lot easier to get into. You don’t need a computer science degree to work with Low-code technologies. In fact, most Mendix developers come from completely different academic fields. I’ve had the pleasure of working with Mendix developers with diverse academic backgrounds such as biomedical science, psychology, and industrial engineering. And while the IT sector continues to be a male-dominated world, this stereotype doesn’t hold up as much in Low-code. Women even outnumbered the men in my team at one point.\nThis diversity in backgrounds leads to Low-code developers often taking on more diverse responsibilities. It is no coincidence that Low-code developers are often referred to as business engineers, although I still prefer to refer to us as developers. A business engineer forms a hybrid between a business administration expert and a software engineer. It means they can understand and translate business needs into software requirements, but also transform those requirements into real software. Most of your day is usually spent developing, but you can be involved in many more parts of the software development process. At my previous employer, it was no exception that business engineers were involved in other activities such as sales and user experience (UX) design.\nEven within Mendix, you can choose to expand into different territories. You can decide to focus on front-end development by learning everything there is to know about styling or you can become an expert back-end developer integrating your system with other systems. You can also choose to dive deeper into the Mendix core by learning about Java development. Most Mendix developers have specific expertise that they are most interested in, but you’ll most likely work on all those areas at some point. Traditional software engineers don’t often get this luxury.\nAll this means you have a lot of options for career growth. You could start as a basic Mendix developer but later choose to expand into other territories. If you find that you have a knack for selling the platform, you can do that alongside your main gig. If you feel comfortable designing applications before building them, there’s usually an opening to do so.\nA lot of that flexibility also comes down to companies supporting your preferences. Most companies active in Low-code are great places to work. They have to be, in order to attract and retain the right people. If you haven’t noticed, Low-code is booming and so are job openings. Finding new business engineers is challenging and companies want to keep employees happy and satisfied. It also means that if you choose to work in Low-code, you’ll be almost certain of having a job for the next decade or so. And while something like job security sounds like the dullest topic to talk about at first, the feeling that you can find a job anywhere is truly comforting.\nIf you want to work as a Mendix developer you have a number of options. If you choose to work in consultancy, you’ll have the opportunity to work on projects for different companies. It’s a great way to gain professional experience in different environments. In the past four years, I worked for companies of all shapes and sizes in fields such as finance, insurance, health, and government. If you are unsure what you want for your career, working in consultancy is the perfect way to explore your options.\nAlternatively, you can choose to work for a company that uses Mendix apps directly. Or you can choose to apply for a job at Mendix itself. As I don’t have hands-on experience with either, I don’t feel qualified to talk about those options, but I’ve heard positive stories about both.\nThe freedom and growth opportunities that you get as a Mendix developer are great. And you can even decide to move into freelancing, as I recently did. Whatever you decide, as a Mendix developer you’ll always have the satisfaction of building amazing digital applications with real-world impact.\nThis blog post reflects my personal beliefs only. It was not written in conjunction with Mendix or my previous employer Finaps. The first version of this article was already published during my time there. This updated article reflects my latest thoughts on the subject.\n","permalink":"https://maismaus.github.io/blog/why-working-as-a-mendix-developer-is-great/","summary":"\u003cp\u003e\u003cem\u003eHere’s the deal\u003c/em\u003e — you’re in your twenties, freshly graduated from college. Maybe you’ve flirted with coding in the past, or hacked your way through a few websites, and generally, you have an affinity with modern tech. Or perhaps you are a psych student who enjoyed working with statistics, and you’re starting to realize that you have to pay those bills eventually.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSooner or later, you’ll need to find yourself one of those “tech jobs”.\u003c/strong\u003e\u003c/p\u003e","title":"Why working as a Mendix developer is great"},{"content":"In september 2021 begon ik als freelancer. Ik had verschillende redenen om als zelfstandige te willen beginnen. Voornamelijk de vrijheid van het freelancen sprak mij aan. Over de autonomie beschikken om te mogen bepalen hoe veel vakantie je opneemt, welke opdracht je aanneemt en welke richting je op wilt bewegen als professional. Maar er speelden ook veel onzekerheden: hoe kom ik aan een opdracht en hoe is het om geen directe collega\u0026rsquo;s te hebben.\nIk krijg regelmatig de vraag hoe het zetten van de stap mij is bevallen. Daarom heb ik mijn gedachten in deze blogpost opgeschreven. Ik structureer mijn bevindingen als de overpeinzingen die ik had voordat ik begon en die ik nu achteraf vanuit mijn ervaring bespreek. Onderaan vind je ook nog een lijstje van praktische to-dos. Ik werk zelf als low-code Mendix ontwikkelaar, maar veel van deze bevindingen gelden ook voor ontwikkelaars in andere technologieën.\n🇬🇧 Experiences may vary. My only experience so far has been in the Dutch market as a native Dutch speaker. Therefore, I wrote this post exclusively in Dutch.\nAls freelancer moet je zelf aan opdrachten komen Ik begin met het grootste twijfelpunt voor de meeste mensen (inclusief ikzelf) om de stap te zetten: het vinden van een opdracht. Voor mensen in loondienst is dit vaak het belangrijkste punt dat de grootste twijfel oplevert.\nVoor mij was dit uiteindelijk zo gepiept. Zodra ik serieus op zoek ging kwam er toevallig een leuke opdracht voorbij op freelance.nl waar ik op reageerde en direct aan de slag kon. Er zijn veel bedrijven die op zoek zijn naar tijdelijke krachten. Ze zoeken bijvoorbeeld iemand op projectbasis of ze kunnen niemand vinden voor een baan in loondienst en zetten daarom een freelancer in. Zodra je je in dat wereldje gaat oriënteren ontdek je vanzelf alle mogelijkheden. Er is een enorme vraag naar ervaren ontwikkelaars en er is dus een ruim aanbod aan projecten om uit te kiezen. Maar uiteraard geldt: your mileage may vary. Het is natuurlijk afhankelijk van je eigen initiatief, ervaring en locatie.\nJe kunt het ook omdraaien. Je mag je eigen opdrachten uitkiezen. Je bent niet afhankelijk van de salesafdeling van het bedrijf waar je werkt, maar je mag zelf kiezen wat je belangrijk vindt in een opdracht. De reisafstand, de grootte van het bedrijf of de mensen die er rondlopen. Vaak wordt er door werkgevers wel enige rekening mee gehouden, maar soms is er geen andere opdracht beschikbaar. Dan zal je toch een vervelende opdracht moeten doen. De werkgever heeft dan ook het recht om dat van je te eisen. Zint een opdracht als freelancer je niet? Dan stap je gewoon op.\nPortalen Afhankelijk van de technologie waar je mee werkt vind je opdrachten op verschillende portalen. Voor Mendix vind je het meeste op freelance.nl, en via je netwerk op LinkedIn. Op Headfirst verschijnen de meeste overheidsopdrachten. Ik gebruik dit topic om op de hoogte te blijven van de reuring onder gelijkgezinden. In de eerste post vind je uitgebreide informatie over het starten als zelfstandige.\nTussenpartijen Door tussenpartijen word ik veel benaderd voor projecten. Ik heb wisselende ervaringen met recruiters. Sommigen doen erg hun best en denken met je mee. Anderen hebben je LinkedIn profiel niet eens bekeken voordat ze je een bericht sturen. Ik probeer het zelf zonder tussenpartij als een recruiter te regelen. Ik heb begrepen dat je voor sommige opdrachten er niet onderuit komt. Bij overheden is het vaak een vereiste om een contract via een broker te regelen vanwege de aanbestedingsregels. Zelf heb ik daar geen ervaring mee en heb ik een contract direct bij de klant. Tussenpartijen snoepen altijd een deel van je uurtarief af. Dit kan erg varieren van 30% (zoals in een midlance constructie) tot 3 euro per uur (via Headfirst). Ik vind het zelf hoe dan ook erg zonde van het geld gezien de hoeveelheid werk die ze over het algemeen voor je verrichten.\nAls freelancer moet je zelf je administratie doen Eigenlijk vond ik dit al met al tegenvallen. Je kunt dit op verschillende manieren aanpakken. Ik heb graag mijn zaken goed op orde, maar daar komt veel uitzoekwerk bij kijken. Je moet weten hoe de belastingen werken, hoe je je facturen verwerkt en hoe je je uren goed registreert. Je moet weten wat het urencriterium is en wat het voor je betekent. Je moet weten hoe je een auto op de zaak zet, en of het handiger is om je auto privé te rijden en de kilometers zakelijk te declareren. Daarnaast moet je ook verzekeringen regelen, over je pensioen nadenken, nadenken over arbeidsongeschiktheid, enzovoort. Vervolgens moet je ook weten hoe je dit alles correct registreert in je administratie.\nZo sloot ik onlangs een abonnement voor Adobe CC af, maar op de factuur stond dat de BTW naar mij verlegd was. Vervolgens ben ik een tijd bezig geweest met uitzoeken wat BTW verleggen betekent, en hoe ik dat correct verwerk in mijn administratie.\nDaar moet bij gezegd worden dat een goed boekhoudprogramma je al veel werk uit handen neemt. Ik gebruik zelf MoneyMonk waar ik erg tevreden over ben. Als je deze link gebruikt krijg je het eerste jaar 50% korting (en ik een cadeaubon van 25 euro). Je kunt ook een boekhouder nemen, hoewel ik daar zelf niet voor gekozen heb. Wel heb ik binnenkort een eenmalig gesprek met een boekhouder om het boekjaar 2021 door te nemen.\nHet grootste deel van het werk is eenmalig uitzoekwerk. Als vervolgens alles loopt, dan loopt het. Iedere maand ben ik tien minuten bezig mijn uren te registreren en mijn facturen in mijn boekhoudprogramma te zetten.\nAls freelancer moet je zelf je tarief bepalen Een lastig punt waar vaak geheimzinnig over gedaan wordt. Hoe dan ook kan ik je garanderen dat je als freelancer meer kunt verdienen dan bij een detacheerder. Bij een detacheerder moeten immers meer mensen dan jijzelf betaald worden van jouw uurtarief. Wel liggen uurtarieven van detacheerders over het algemeen iets hoger dan voor zelfstandigen. Je kunt dus niet je huidige uurtarief in loondienst vragen als je start als zelfstandige. Bedrijven betalen het hogere tarief soms liever omdat ze dan meer zekerheid hebben dat er een alternatief ingebracht kan worden bij uitval. Als een zelfstandige uitvalt, is er niet iemand anders die de taken kan overnemen.\nWat mij betreft is dit overigens een valse zekerheid. Je kunt sowieso niet iemand die uitvalt zomaar 1-op-1 vervangen, omdat er altijd ervaring wegvalt. En detacheerders hebben meestal niet mensen op de bank zitten, klaar om iemand te vervangen.\nHet uurtarief voor een zelfstandige Mendix ontwikkelaar ligt, afhankelijk van de rol en je ervaring, tussen de 65 en 125 euro.\nAls freelancer heb je minder zekerheid Dit is voor veel mensen een belangrijk twijfelpunt om de stap naar freelancen te maken. Ik moet hierbij zeggen dat dit voor mij geen groot punt was. Ik had een reserve opgebouwd om een periode zonder opdracht te kunnen overbruggen en omdat ik op goede voet wegging bij mijn werkgever was ik daar altijd welkom om terug te keren.\nAls je een vast contract hebt, kan een werkgever je niet zomaar ontslaan. Je hebt dus een zekere garantie dat iedere maand het salaris op je rekening staat. Als freelancer zijn je inkomsten grilliger: als je op vakantie gaat verdien je minder en je weet nooit zeker wanneer de factuur betaald wordt en het geld op je rekening staat.\nMijn mening is dat zo lang je ervoor zorgt dat je voldoende buffer hebt om periodes zonder salaris te overbruggen, je ruim voldoende zekerheid hebt. En omdat je uurtarief hoger is dan je salaris in loondienst, is het opbouwen van voldoende buffer des te makkelijker.\nAls freelancer moet je voldoende vaardigheid bezitten Over het algemeen wordt er van een freelancer een bepaalde mate van zelfstandigheid verwacht. Bedrijven huren een freelancer in met de verwachting dat die snel up and running zal zijn. Omdat je niet in dienstverband zit gaat de opdrachtgever niet in je investeren zoals een traditionele werkgever dat doet. Je bent zelf verantwoordelijk voor je ontwikkeling. Maar je hebt dus ook geen bila\u0026rsquo;s en andere verplichtingen waar veel freelancers een hekel aan hebben.\nIn mijn ervaring geeft het meer voldoening om aan je eigen ontwikkeling te werken als freelancer. In loondienst krijg je vaak van hogerhand doelen opgelegd, waar je niet per definitie achter staat. Of misschien wel achter het idee, maar niet achter de uitvoering. Als je in loondienst iets gaafs doet, draag je bij aan de prestige van het bedrijf. Als freelancer draag je bij aan je eigen \u0026lsquo;merk\u0026rsquo;. Dat geeft mij meer voldoening.\nDe meeste opdrachten voor Mendix ontwikkelaars vereisen 3 jaar ervaring en minimaal een Advanced certificaat.\nAls freelancer heb je geen directe collega\u0026rsquo;s meer Als je werkt in loondienst via een detacheerder heb je vaak de detacheerder als \u0026rsquo;thuisbasis\u0026rsquo;. Je hebt dan altijd je directe collega\u0026rsquo;s om op terug te vallen met vragen. Dit valt als freelancer weg. Je bent afhankelijker van de opdracht en de mensen die daar rondlopen. Sommige bedrijven nemen freelancers mee in sociale bedrijfsactiviteiten, anderen hebben exclusieve activiteiten enkel voor werknemers.\nWel zoeken freelancers elkaar gauw op en zijn er altijd communities te vinden. In het geval van Mendix heb je bijvoorbeeld de Mendix Community op Slack, waar je in ieder geval buiten je opdracht contact kunt hebben met mensen in de gemeenschap.\nDe stap zetten De de eerste stap is altijd het aanvragen van een KvK-nummer. Een KvK-nummer is essentieel voor het starten als zelfstandige; je hebt hem voor bijna alles nodig. Voor mezelf heb ik tijdens het opstarten bijgehouden wat er verder allemaal bij kwam kijken. Het verschilt per persoon waar je prioriteiten liggen. Het is aan jou zelf om te onderzoeken wat je hiervan nodig acht.\nAdministratie KvK nummer aanvragen Zakelijke rekening openen Boekhoudprogramma uitkiezen Boekhouder aannemen Laptop bestellen Website maken CV updaten LinkedIn bijwerken Zakelijk e-mail adres aanmaken NS Zakelijk aanvragen Template overeenkomst van opdracht opstellen (OvO) Office 365 licentie Tankpas aanvragen Groothandel passen aanvragen Inschrijvingen verzekeringen Inschrijven ZZP Nederland of Ondernemerscollectief Arbeidsongeschiktheidsverzekering (AOV) Aansprakelijkheidsverzekering bedrijven (AVB) Beroepsaansprakelijkheidsverzekering (BAV) Overlijdensrisicoverzekering (ORV) Inschrijven ZZP marktplaatsen (freelance.nl, Headfirst, brainnet etc.) Omzetten naar zakelijke rekening: Telefoonabonnement Internet- en televisieabonnement Autoverzekering Wegenbelasting ANWB Belastingdienst standaard rekeningnummer ","permalink":"https://maismaus.github.io/blog/freelance-ervaringen-en-tips/","summary":"\u003cp\u003eIn september 2021 begon ik als freelancer. Ik had verschillende redenen om als zelfstandige te willen beginnen. Voornamelijk de vrijheid van het freelancen sprak mij aan. Over de autonomie beschikken om te mogen bepalen hoe veel vakantie je opneemt, welke opdracht je aanneemt en welke richting je op wilt bewegen als professional. Maar er speelden ook veel onzekerheden: hoe kom ik aan een opdracht en hoe is het om geen directe collega\u0026rsquo;s te hebben.\u003c/p\u003e","title":"Wat ik geleerd heb van starten als freelancer"},{"content":"As a Mendix developer, you are likely working with Studio Pro every day. However, there are many simple tricks that you can use in your day to day work that make your life a little bit easier. In this blog post, I will go over some of the pro tips that I have gathered during my time as a Mendix developer.\n1. Use dissolve container to quickly remove a container Got a container you don\u0026rsquo;t need anymore, but you need to save the content within it? Stop dragging all items out of the container before deleting it! Simply right click the container and select dissolve container to remove the container but keep the content contained within it.\n2. Use inline snippet to return snippet contents back to a page Similar to dissolve container, you can right click a snippet and select inline snippet to put all snippet contents back on a page instantly.\n3. Quickly set the return value in a microflow I used to select the entity to return from a microflow by:\nDouble clicking the End event Selecting the return Entity type Entering a variable to return However, you can also right click an activity that contains a variable and press Set as return value to do all of those things at once!\n4. Instantly swap input widget types for enumerations and booleans As you probably know, you can use different types of input widgets for enumerations and booleans. Enumeration attributes support drop down and radio button widgets, and booleans support radio buttons and checkboxes. Did you know you can right click those input widgets to instantly swap between the two types of input widgets?\n5. Dragging and dropping Drag and drop is a pattern which is used often throughout Studio Pro. Did you know you can use it in many more instances than you might be aware of?\nDrag flows from the Explorer into Microflows You can either drag a microflow into a blank area of a microflow to create a new Call Microflow activity, or onto an existing Call Microflow activity to replace the subflow call with another.\nDrag items from the Explorer into pages You can drag most items from the Project Explorer into a page to add a widget to your page instantly. You can drag them onto existing widgets or onto blank targets to create a new widget.\nSome examples of drag and drop actions that you can do:\nDrag a microflow onto a button to set that microflow as the button action Drag a page into a page view to insert a button linking to that page Drag a microflow onto a dataview to set that microflow as the datasource Microflows must return a list before you can drag them onto a listview, and they must return a single object before you can drag them onto a data view.\nDrag connector items into data views to insert input widgets If you have your selection inside a dataview, the connector tab will show you the attributes for the entity in that dataview. You can use the connector to drag any of those attributes into the dataview to instantly create an input widget for that attribute. No more scrolling through the Toolbox to find the correct widget, it automatically select the right type of input widget for that attribute!\n6. Keyboard shortcuts I\u0026rsquo;m sure you are using some of the shortcut keys that Mendix Studio Pro offers, but are you making full use of them? These are the shortcuts I use every single day:\nCtrl + Enter to close any window that you currently have open Ctrl + G to open the quick find menu Ctrl + Space to open the autofill option when entering a variable or XPath F5 to run your app F9 to view your app Ctrl + W to close any open tab Shift + mouse wheel scrolls through a microflow horizontally Refer to the Mendix documentation to view all shortcut keys configured for Studio Pro\n7. Use the custom caption option to improve legibility of your microflows When you call a submicroflow from another microflow, Mendix auto generates the caption that is displayed from the name of the submicroflow. However, you can greatly improve legibility by adding a custom caption to your submicroflow call. Just select the action and press Shift + F2 to enter a custom caption. This way, you can explain in your own language what the microflow is doing.\n8. Add custom design properties to your project In the appearance tab, you can assign design properties to apply to your page content. Selecting a design property adds the corresponding class to that element.\nDesign properties are defined in design-projecties.json in your project directory. You can add custom design properties to your project to allow all team members to easily discover available custom classes.\nFor instance, you can add the \u0026lsquo;hide empty\u0026rsquo; design property to all listviews by adding the code below to design-properties.json. From then on, you can select it as a design property in the appearance tab.\n\u0026#34;ListView\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;Hide empty\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;Toggle\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Hides the \u0026#39;no items found\u0026#39; in empty lists.\u0026#34;, \u0026#34;class\u0026#34;: \u0026#34;listview-hide-empty\u0026#34; } ], Design properties do nothing besides adding a class to the element, so make sure you have appropriate styling available in your stylesheets to apply to the element!\n9. Align your microflows automatically In a recent release, Mendix added the option to align your microflow actions automatically. Just select the elements that you want to align, and right click to select the correct option. For instance, distribute horizontally automatically adds the same space between all selected actions.\nFrom this: To this! More useful when you have more than a single action, obviously.\n10. Use the documentation export to quickly generate app documentation As you are probably aware, Mendix allows you to document content of your app in most places. You can add documentation to domain models, entities, microflows, and even separate microflow parameters. But did you know there is a built-in export feature for these types of documentation files? Just right click your app in the Explorer and select \u0026lsquo;Export documentation\u0026rsquo;. This automatically generates a comprehensive .html file with all your project documentation!\nI hope these tips will help you become a true Mendix Pro. If you have any thoughts, feel free to share them. And remember, stay curious!\n","permalink":"https://maismaus.github.io/blog/10-tips-to-speed-up-your-Mendix-development/","summary":"\u003cp\u003eAs a Mendix developer, you are likely working with Studio Pro every day. However, there are many simple tricks that you can use in your day to day work that make your life a little bit easier. In this blog post, I will go over some of the pro tips that I have gathered during my time as a Mendix developer.\u003c/p\u003e\n\u003ch3 id=\"1-use-dissolve-container-to-quickly-remove-a-container\"\u003e1. Use dissolve container to quickly remove a container\u003c/h3\u003e\n\u003cp\u003eGot a container you don\u0026rsquo;t need anymore, but you need to save the content within it? Stop dragging all items out of the container before deleting it! Simply right click the container and select \u003ccode\u003edissolve container\u003c/code\u003e to remove the container but keep the content contained within it.\u003c/p\u003e","title":"Here's 10 tips you wish you knew earlier to speed up your Mendix development"},{"content":"","permalink":"https://maismaus.github.io/cv/","summary":"cv","title":"CV"},{"content":".mx-dataview { \u0026amp;.dataview-h-100 { height: 100%; .mx-dataview-content { height: 100%; } } } ","permalink":"https://maismaus.github.io/docs/cssdoc/dataview/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e.mx-dataview {\n    \u0026amp;.dataview-h-100 {\n        height: 100%;\n\n        .mx-dataview-content {\n            height: 100%;\n        }\n    }\n}\n\u003c/code\u003e\u003c/pre\u003e","title":"Dataview helpers"},{"content":"Copy to clipboard navigator.clipboard.writeText(content);\nOpen url with target window.open(url, target);\nJS_OpenURL_WithTarget.mpk\nPrint current page window.print();\nJS_Print.mpk\nToggle class on element const element = document.querySelector(querySelector); if (element) { if (element.classList.contains(className)) { element.classList.remove(className); } else { element.classList.add(className); } return true; } else return false; JS_ToggleClassOnElement.mpk\nSelect all text on input focus Set this JavaScript action on the OnChange nanoflow\nconst activeElement = document.activeElement; if (activeElement.tagName === \u0026#34;INPUT\u0026#34;) activeElement.select(); JS_Input_SelectOnFocus.mpk\nScroll to element // BEGIN EXTRA CODE function scrollToElement(targetSelector, block, inline) { const elements = document.querySelectorAll(targetSelector); if (elements) { const element = elements[elements.length -1]; if (element){ console.debug(element); element.scrollIntoView({ behavior: \u0026#34;smooth\u0026#34;, block: block, inline: inline }); return true; } } return false; } // END EXTRA CODE if (scrollToElement(targetSelector, block, inline)) return; //Retry because sometimes the DOM element is not loaded yet. console.log(\u0026#34;no element found\u0026#34;); setTimeout(function() { scrollToElement(targetSelector, block, inline); },200); JS_ScrollToElement.mpk\nScroll to error const validationMessage = document.querySelector(\u0026#34;.mx-validation-message\u0026#34;); if(validationMessage) { scrollIntoView(validationMessage); return; } const alert = document.querySelector(\u0026#34;.alert.alert-danger\u0026#34;); if(alert) { scrollIntoView(alert); return; } console.warn(\u0026#34;No validation message found.\u0026#34;); JS_ScrollToError.mpk\n","permalink":"https://maismaus.github.io/docs/javascriptdoc/jsactions/","summary":"\u003ch3 id=\"copy-to-clipboard\"\u003eCopy to clipboard\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003enavigator.clipboard.writeText(content);\u003c/code\u003e\u003c/p\u003e\n\u003ch3 id=\"open-url-with-target\"\u003eOpen url with target\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003ewindow.open(url, target);\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/mpk/JS_OpenURL_WithTarget.mpk\"\u003eJS_OpenURL_WithTarget.mpk\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"print-current-page\"\u003ePrint current page\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003ewindow.print();\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/mpk/JS_Print.mpk\"\u003eJS_Print.mpk\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"toggle-class-on-element\"\u003eToggle class on element\u003c/h3\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003econst element = document.querySelector(querySelector);\nif (element) {\n    if (element.classList.contains(className)) {\n        element.classList.remove(className);\n    }\n    else {\n        element.classList.add(className);\n    }\n    return true;\n}\nelse return false;\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ca href=\"/mpk/JS_ToggleClassOnElement.mpk\"\u003eJS_ToggleClassOnElement.mpk\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"select-all-text-on-input-focus\"\u003eSelect all text on input focus\u003c/h3\u003e\n\u003cp\u003eSet this JavaScript action on the OnChange nanoflow\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003econst activeElement = document.activeElement;\nif (activeElement.tagName === \u0026#34;INPUT\u0026#34;) activeElement.select();\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ca href=\"/mpk/JS_Input_SelectOnFocus.mpk\"\u003eJS_Input_SelectOnFocus.mpk\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"scroll-to-element\"\u003eScroll to element\u003c/h3\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e// BEGIN EXTRA CODE\nfunction scrollToElement(targetSelector, block, inline) {\n\tconst elements = document.querySelectorAll(targetSelector);\n    if (elements) {\n        const element = elements[elements.length -1];\n\t\tif (element){\n\t\t\tconsole.debug(element);\n\t\t\telement.scrollIntoView({\n\t\t\t\tbehavior: \u0026#34;smooth\u0026#34;,\n\t\t\t\tblock: block,\n\t\t\t\tinline: inline\n\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n// END EXTRA CODE\n\nif (scrollToElement(targetSelector, block, inline)) return;\n\n//Retry because sometimes the DOM element is not loaded yet. \nconsole.log(\u0026#34;no element found\u0026#34;);\nsetTimeout(function() {\n    scrollToElement(targetSelector, block, inline);\n},200);\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ca href=\"/mpk/JS_ScrollToElement.mpk\"\u003eJS_ScrollToElement.mpk\u003c/a\u003e\u003c/p\u003e","title":"JavaScript Actions"},{"content":"This section describes various JavaScript code snippets using the Mendix JavaScript client-side API. For the complete documentation, refer to the Mendix docs\nSession management Remove sessions This can be useful if you\u0026rsquo;ve exceeded the user limit in your local environment\nmx.data.get({ xpath: \u0026#34;//System.Session\u0026#34;, callback: function (session) { var sessionArray = []; var sessionLength = session.length; for (var i = 0; i \u0026lt; session.length; i++) { sessionArray.push(session[i]._guid); } mx.data.remove({ guids: sessionArray, callback: function () { console.log(sessionLength + \u0026#34; sessions removed\u0026#34;); }, error: function (e) { console.log(\u0026#34;Could not remove objects:\u0026#34;, e); }, }); }, }); Session bookmarklet javascript:(function()%7Bmx.data.get(%7Bxpath%3A%20%22%2F%2FSystem.Session%22%2Ccallback%3A%20function%20(session)%20%7Bvar%20sessionArray%20%3D%20%5B%5D%3Bfor%20(var%20i%20%3D%200%3B%20i%20%3C%20session.length%3B%20i%2B%2B)%20%7BsessionArray.push(session%5Bi%5D._guid)%3B%7Dmx.data.remove(%7Bguids%3A%20sessionArray%2Ccallback%3A%20function%20()%20%7Bconsole.log(toString(session.length)%20%2B%20%22sessions%20removed%22)%3B%7D%2Cerror%3A%20function%20(e)%20%7Bconsole.log(%22Could%20not%20remove%20objects%3A%22%2C%20e)%3B%7D%7D)%3B%7D%7D)%7D)() Logging in var xhr = new XMLHttpRequest(); xhr.open(\u0026#34;POST\u0026#34;, location.origin + \u0026#34;/xas/\u0026#34;, true); xhr.setRequestHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;); xhr.send( JSON.stringify({ action: \u0026#34;login\u0026#34;, params: { username: \u0026#34;MxAdmin\u0026#34;, password: \u0026#34;1\u0026#34;, }, }) ); xhr.onreadystatechange = function () { window.location = \u0026#34;index.html\u0026#34;; }; Login bookmarklet Bookmark this and press the bookmark to login. Replace username and password with your own (currently: MxAdmin, 1).\njavascript:(function()%7Bvar%20xhr%3Dnew%20XMLHttpRequest()%3Bxhr.open(%22POST%22%2Clocation.origin%2B%22%2Fxas%2F%22%2Ctrue)%3Bxhr.setRequestHeader(%22Content-Type%22%2C%22application%2Fjson%22)%3Bxhr.send(JSON.stringify(%7Baction%3A%22login%22%2Cparams%3A%7Busername%3A%22MxAdmin%22%2Cpassword%3A%221%22%2C%7D%2C%7D))%3Bxhr.onreadystatechange%3Dfunction()%7Bwindow.location%3D%22index.html%22%3B%7D%7D)()%3B Logout bookmarklet javascript:(function()%7Bmx.logout()%3B%7D)()%3B\nGet current page name var x = mx.ui.getContentForm().path; var y = x.split(\u0026#39;/\u0026#39;); var z = y[1].split(\u0026#39;.\u0026#39;); var pageName = z[0]; return pageName; Page name bookmarklet javascript:(function(){varpageTitle=mx.ui.getContentForm().path.replace(\u0026#34;.page.xml\u0026#34;,\u0026#34;\u0026#34;);varpageTitleArr=pageTitle.split(\u0026#34;/\u0026#34;);varmoduleName=pageTitleArr[0];varpageName=pageTitleArr[1];prompt(\u0026#34;Module:\u0026#34;+moduleName,pageName);})(); Object handling Log GUID of object retrieved through association mx.data.get({ xpath: \u0026#34;//Onboarding.Application[RegisterID=257]/Onboarding.Applicant_Application/Onboarding.Applicant\u0026#34;, callback: function (obj) { console.log(obj[0]._guid); }, }); Retrieve objects with guid mx.data.get({ guids: [\u0026#34;123456\u0026#34;, \u0026#34;456789\u0026#34;], callback: function (objs) { console.log(\u0026#34;Received \u0026#34; + objs.length + \u0026#34; MxObjects\u0026#34;); }, }); Execute microflow with guid as parameter mx.data.action({ params: { applyto: \u0026#34;selection\u0026#34;, actionname: \u0026#34;Onboarding.ACT_NewApplication\u0026#34;, //Execute microflow guids: [\u0026#34;20829148276589155\u0026#34;], //Guid as parameter }, origin: this.mxform, callback: function (obj) { // expect single MxObject alert(obj.get(\u0026#34;manufacturer\u0026#34;)); }, error: function (error) { alert(error); }, }); Get, edit and commit mx.data.get({ xpath: \u0026#34;//Onboarding.Applicant[RegisterID=1374]\u0026#34;, //Get applicant with ID 1374 callback: function (obj) { obj[0].set(\u0026#34;RegisterID\u0026#34;, \u0026#34;1375\u0026#34;); //Set marital status to \u0026#39;02\u0026#39; mx.data.commit({ mxobj: obj[0], //Commit applicant callback: function () { console.log(\u0026#34;Object committed\u0026#34;); }, error: function (e) { console.error(\u0026#34;Could not commit object:\u0026#34;, e); }, }); }, }); Advanced object editing Get an object, Get another object, Set association of first object to second object, Commit second object mx.data.get({ xpath: \u0026#39;//Onboarding.Applicant[RegisterID=1374]\u0026#39;, callback: function (applicant) { console.log(applicant[0]); mx.data.get({ xpath: \u0026#39;//Onboarding.Country[Name=\u0026#34;Germany\u0026#34;]\u0026#39;, callback: function (obj) { obj[0].addReference(\u0026#34;Onboarding.Application_Country\u0026#34;, country[0]._guid); console.log(obj[0]); mx.data.commit({ mxobj: obj[0], //Commit applicant callback: function () { console.log(\u0026#34;Object committed\u0026#34;); }, error: function (e) { console.error(\u0026#34;Could not commit object:\u0026#34;, e); }, }); }, }); }, }); Remove object mx.data.get({ xpath: \u0026#34;//Onboarding.Applicant[RegisterID=84427]\u0026#34;, callback: function (applicant) { //Return list of tasks mx.data.remove({ //Remove first item in the list guid: applicant[0]._guid, callback: function () { console.log(applicant[0]); }, error: function (e) { console.log(\u0026#34;Could not remove objects:\u0026#34;, e); }, }); }, }); Open pages Open page in content mx.ui.openForm(\u0026#34;Login/Login.page.xml\u0026#34;, { location: \u0026#34;content\u0026#34;, callback: function (form) { console.log(form.id); }, }); Open page in popup mx.ui.openForm(\u0026#34;Onboarding/Application.page.xml\u0026#34;, { location: \u0026#34;modal\u0026#34;, callback: function () {}, }); OpenForm2 Undocumented mx.ui OpenForm replacement function.\nlet e = \u0026#34;Portal/MyDoctors.page.xml\u0026#34;; //PageTitle let n = {}; // ? let r = null; // ? let i = { \u0026#34;place\u0026#34;: \u0026#34;content\u0026#34;, \u0026#34;listeners\u0026#34;: {}, \u0026#34;suspended\u0026#34;: false, \u0026#34;title\u0026#34;: \u0026#34;Homepage\u0026#34;, \u0026#34;historyId\u0026#34;: \u0026#34;historyId_tks_0\u0026#34;, \u0026#34;storeBackend\u0026#34;: { \u0026#34;recordGroups\u0026#34;: {}, \u0026#34;mirrorCount\u0026#34;: {}, \u0026#34;usedSlotsCount\u0026#34;: {}, \u0026#34;slotObservers\u0026#34;: {} }, \u0026#34;name\u0026#34;: \u0026#34;Portal.Home_Web\u0026#34; } // History? let s = { \u0026#34;name\u0026#34;: \u0026#34;Portal/MyDoctors.page.xml\u0026#34;, \u0026#34;location\u0026#34;: \u0026#34;content\u0026#34;, \u0026#34;allowedRoles\u0026#34;: [ \u0026#34;Administrator\u0026#34;, \u0026#34;User\u0026#34; ] } // Parameters? let c = 0; // Pages to close mx.ui.openForm2(e,n,r,i,s,c); ","permalink":"https://maismaus.github.io/docs/jsdoc/javascriptapi/","summary":"\u003cp\u003eThis section describes various JavaScript code snippets using the Mendix JavaScript client-side API.\nFor the complete documentation, refer to the \u003ca href=\"https://apidocs.rnd.mendix.com/10/client/index.html\"\u003eMendix docs\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"session-management\"\u003eSession management\u003c/h2\u003e\n\u003ch3 id=\"remove-sessions\"\u003eRemove sessions\u003c/h3\u003e\n\u003cp\u003eThis can be useful if you\u0026rsquo;ve exceeded the user limit in your local environment\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003emx.data.get({\n  xpath: \u0026#34;//System.Session\u0026#34;,\n  callback: function (session) {\n    var sessionArray = [];\n    var sessionLength = session.length;\n    for (var i = 0; i \u0026lt; session.length; i++) {\n      sessionArray.push(session[i]._guid);\n    }\n    mx.data.remove({\n      guids: sessionArray,\n      callback: function () {\n        console.log(sessionLength + \u0026#34; sessions removed\u0026#34;);\n      },\n      error: function (e) {\n        console.log(\u0026#34;Could not remove objects:\u0026#34;, e);\n      },\n    });\n  },\n});\n\u003c/code\u003e\u003c/pre\u003e\u003ch4 id=\"session-bookmarklet\"\u003eSession bookmarklet\u003c/h4\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ejavascript:(function()%7Bmx.data.get(%7Bxpath%3A%20%22%2F%2FSystem.Session%22%2Ccallback%3A%20function%20(session)%20%7Bvar%20sessionArray%20%3D%20%5B%5D%3Bfor%20(var%20i%20%3D%200%3B%20i%20%3C%20session.length%3B%20i%2B%2B)%20%7BsessionArray.push(session%5Bi%5D._guid)%3B%7Dmx.data.remove(%7Bguids%3A%20sessionArray%2Ccallback%3A%20function%20()%20%7Bconsole.log(toString(session.length)%20%2B%20%22sessions%20removed%22)%3B%7D%2Cerror%3A%20function%20(e)%20%7Bconsole.log(%22Could%20not%20remove%20objects%3A%22%2C%20e)%3B%7D%7D)%3B%7D%7D)%7D)()\n\u003c/code\u003e\u003c/pre\u003e\u003ch3 id=\"logging-in\"\u003eLogging in\u003c/h3\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003evar xhr = new XMLHttpRequest();\nxhr.open(\u0026#34;POST\u0026#34;, location.origin + \u0026#34;/xas/\u0026#34;, true);\nxhr.setRequestHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;);\nxhr.send(\n  JSON.stringify({\n    action: \u0026#34;login\u0026#34;,\n    params: {\n      username: \u0026#34;MxAdmin\u0026#34;,\n      password: \u0026#34;1\u0026#34;,\n    },\n  })\n);\nxhr.onreadystatechange = function () {\n  window.location = \u0026#34;index.html\u0026#34;;\n};\n\u003c/code\u003e\u003c/pre\u003e\u003ch4 id=\"login-bookmarklet\"\u003eLogin bookmarklet\u003c/h4\u003e\n\u003cp\u003eBookmark this and press the bookmark to login. Replace username and password with your own (currently: MxAdmin, 1).\u003c/p\u003e","title":"JavaScript API"},{"content":"This section describes various bookmarklets that you can use in the bookmarks toolbar. They contain various JavaScript functions for easy access.\nLogin Login as a user. Currently set up with user MxAdmin and password 1.\njavascript:(function()%7Bvar xhr %3D new XMLHttpRequest()%3B%0Axhr.open(\u0026#34;POST\u0026#34;%2C location.origin %2B \u0026#34;%2Fxas%2F\u0026#34;%2C true)%3B%0Axhr.setRequestHeader(\u0026#34;Content-Type\u0026#34;%2C \u0026#34;application%2Fjson\u0026#34;)%3B%0Axhr.send(%0AJSON.stringify(%7B%0A action%3A \u0026#34;login\u0026#34;%2C%0A params%3A %7B%0A username%3A \u0026#34;MxAdmin\u0026#34;%2C%0A password%3A \u0026#34;1\u0026#34;%2C%0A %7D%2C%0A %7D)%0A)%3B%0Axhr.onreadystatechange %3D function () %7B%0A window.location %3D \u0026#34;index.html\u0026#34;%3B%0A%7D%3B%7D)()%3B Logout Logout as current user.\njavascript:mx.logout(); Session bookmarklet Remove all sessions (if you have access rights to it).\njavascript:(function()%7Bmx.data.get(%7Bxpath%3A%20%22%2F%2FSystem.Session%22%2Ccallback%3A%20function%20(session)%20%7Bvar%20sessionArray%20%3D%20%5B%5D%3Bfor%20(var%20i%20%3D%200%3B%20i%20%3C%20session.length%3B%20i%2B%2B)%20%7BsessionArray.push(session%5Bi%5D._guid)%3B%7Dmx.data.remove(%7Bguids%3A%20sessionArray%2Ccallback%3A%20function%20()%20%7Bconsole.log(toString(session.length)%20%2B%20%22sessions%20removed%22)%3B%7D%2Cerror%3A%20function%20(e)%20%7Bconsole.log(%22Could%20not%20remove%20objects%3A%22%2C%20e)%3B%7D%7D)%3B%7D%7D)%7D)() Go to index.html\njavascript:void(window.location.href = \u0026#39;/index.html\u0026#39;); Show current page details Shows the currently module and pagename in a JavaScript alert.\njavascript:(function()%7Bjavascript%3A(function()%7Bvar pageTitle%3Dmx.ui.getContentForm().path.replace(\u0026#34;.page.xml\u0026#34;%2C\u0026#34;\u0026#34;)%3Bvar pageTitleArr%3DpageTitle.split(\u0026#34;%2F\u0026#34;)%3Bvar moduleName%3DpageTitleArr%5B0%5D%3Bvar pageName%3DpageTitleArr%5B1%5D%3Bprompt(\u0026#34;Module%3A\u0026#34;%2BmoduleName%2CpageName)%3B%7D)()%3B%7D)()%3B ","permalink":"https://maismaus.github.io/docs/jsdoc/javascriptbookmarks/","summary":"\u003cp\u003eThis section describes various bookmarklets that you can use in the bookmarks toolbar. They contain various JavaScript functions for easy access.\u003c/p\u003e\n\u003ch3 id=\"login\"\u003eLogin\u003c/h3\u003e\n\u003cp\u003eLogin as a user. Currently set up with user MxAdmin and password 1.\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ejavascript:(function()%7Bvar xhr %3D new XMLHttpRequest()%3B%0Axhr.open(\u0026#34;POST\u0026#34;%2C location.origin %2B \u0026#34;%2Fxas%2F\u0026#34;%2C true)%3B%0Axhr.setRequestHeader(\u0026#34;Content-Type\u0026#34;%2C \u0026#34;application%2Fjson\u0026#34;)%3B%0Axhr.send(%0AJSON.stringify(%7B%0A        action%3A \u0026#34;login\u0026#34;%2C%0A        params%3A %7B%0A            username%3A \u0026#34;MxAdmin\u0026#34;%2C%0A            password%3A \u0026#34;1\u0026#34;%2C%0A        %7D%2C%0A    %7D)%0A)%3B%0Axhr.onreadystatechange %3D function () %7B%0A    window.location %3D \u0026#34;index.html\u0026#34;%3B%0A%7D%3B%7D)()%3B\n\u003c/code\u003e\u003c/pre\u003e\u003ch3 id=\"logout\"\u003eLogout\u003c/h3\u003e\n\u003cp\u003eLogout as current user.\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ejavascript:mx.logout();\n\u003c/code\u003e\u003c/pre\u003e\u003ch4 id=\"session-bookmarklet\"\u003eSession bookmarklet\u003c/h4\u003e\n\u003cp\u003eRemove all sessions (if you have access rights to it).\u003c/p\u003e","title":"JavaScript Bookmarks"},{"content":".mx-listview { //Display listview items inline \u0026amp;.listview-inline { \u0026gt;ul { \u0026gt;li { display: inline; \u0026gt;.mx-dataview { display: inline; \u0026gt;.mx-dataview-content { display: inline; } } } } } //Display listview items as flex items \u0026amp;.listview-flex { \u0026gt;ul { display: flex; flex-wrap: wrap; \u0026gt;li { \u0026amp;:not(:last-child) { margin-right: $spacing-medium; } } } } \u0026amp;.listview-grid { \u0026gt;ul { @include grid(200px, $spacing-medium); } } //Display listview items as inline blocks \u0026amp;.listview-inline-block { \u0026gt;ul\u0026gt;li { display: inline-block; } } //Ends each listview item with a comma \u0026amp;.listview-comma-separated { \u0026gt;ul\u0026gt;li:not(:last-child) { \u0026amp;:after { content: \u0026#34;, \u0026#34;; } } } //Display bullets in front of listview items \u0026amp;.listview-bullets { \u0026gt;ul { list-style: disc; } } // Hides the \u0026#39;no items found\u0026#39; text on empty lists \u0026amp;.listview-hide-empty { .mx-listview-empty { display: none; } } //Hides the \u0026#39;load more\u0026#39; button in listviews \u0026amp;.listview-hide-load-more { .mx-listview-loadMore { display: none; } } } ","permalink":"https://maismaus.github.io/docs/cssdoc/listview/","summary":"\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e.mx-listview {\n\n    //Display listview items inline\n    \u0026amp;.listview-inline {\n        \u0026gt;ul {\n            \u0026gt;li {\n                display: inline;\n\n                \u0026gt;.mx-dataview {\n                    display: inline;\n\n                    \u0026gt;.mx-dataview-content {\n                        display: inline;\n                    }\n                }\n            }\n        }\n    }\n\n    //Display listview items as flex items\n    \u0026amp;.listview-flex {\n        \u0026gt;ul {\n            display: flex;\n            flex-wrap: wrap;\n\n            \u0026gt;li {\n                \u0026amp;:not(:last-child) {\n                    margin-right: $spacing-medium;\n                }\n            }\n        }\n    }\n\n    \u0026amp;.listview-grid {\n        \u0026gt;ul {\n            @include grid(200px, $spacing-medium);\n        }\n    }\n\n    //Display listview items as inline blocks\n    \u0026amp;.listview-inline-block {\n        \u0026gt;ul\u0026gt;li {\n            display: inline-block;\n        }\n    }\n\n    //Ends each listview item with a comma\n    \u0026amp;.listview-comma-separated {\n        \u0026gt;ul\u0026gt;li:not(:last-child) {\n            \u0026amp;:after {\n                content: \u0026#34;, \u0026#34;;\n            }\n        }\n    }\n\n    //Display bullets in front of listview items\n    \u0026amp;.listview-bullets {\n        \u0026gt;ul {\n            list-style: disc;\n        }\n    }\n\n    // Hides the \u0026#39;no items found\u0026#39; text on empty lists\n    \u0026amp;.listview-hide-empty {\n        .mx-listview-empty {\n            display: none;\n        }\n    }\n\n    //Hides the \u0026#39;load more\u0026#39; button in listviews\n    \u0026amp;.listview-hide-load-more {\n        .mx-listview-loadMore {\n            display: none;\n        }\n    }\n}\n\u003c/code\u003e\u003c/pre\u003e","title":"Listview helpers"}]