{"id":3181,"date":"2018-03-31T01:06:29","date_gmt":"2018-03-31T01:06:29","guid":{"rendered":"http:\/\/13.52.125.237\/?p=3181"},"modified":"2018-04-18T23:14:53","modified_gmt":"2018-04-18T23:14:53","slug":"blaze-to-react","status":"publish","type":"post","link":"https:\/\/sotaenterprises.com\/?p=3181","title":{"rendered":"Blaze to React"},"content":{"rendered":"<p>In early 2017, I decided to convert one of my client\u2019s applications from Blaze to React. This is the story of that conversion effort. If you are a Meteor developer, this post will likely be interesting to you, particularly if you are considering moving to React. For ordinary folks, I\u2019ve done my best to minimize technobabble.<\/p>\n<p>As a byproduct of the conversion process, I created a reusable framework called <em>VXFrame<\/em>. I&#8217;m looking for opportunities to use VXFrame to develop new React systems or to expedite the migration of existing systems from Blaze to React.<\/p>\n<h3>Motivation<\/h3>\n<p>If you have been following Meteor blogs, you are likely aware that React has factionalized the community. Rumblings about React began in 2015 as<i>\u00a0<\/i>Sacha Greif, the author of <em>Discover Meteor<\/em>, published a series of articles that explain how to migrate a Meteor application from Blaze to React. Then, in November 2016, Meteor guru Arunoda Susiripala bid farewell to the Meteor community, moving on to work on Next.js, a minimalistic React framework.<\/p>\n<p>These events piqued my interest, and I began to evaluate React in earnest. I decided to convert a production application named\u00a0<em>Incentive Sherpa\u00a0<\/em>from Blaze to React. I took this aggressive approach because I feel that no tutorial can give one the depth of experience that can be gained by committing to a real project and pushing it through to completion.<\/p>\n<p>I had originally estimated that the project would require 60 days, but it ended up taking over 100 days. To put this in perspective, understand that Sherpa was a medium-sized, multi-tenant SaaS application based on Meteor 1.3. It incorporated a variety of third-party packages that are the hallmarks of a typical enterprise application. Sherpa used the so-called <em>advanced<\/em> Bootstrap implementation which facilitated control over appearance and layout via configuration variables.<\/p>\n<p>Sherpa size metrics:<\/p>\n<ul>\n<li>13 major subsystems<\/li>\n<li>35 Blaze templates<\/li>\n<li>26 routes<\/li>\n<li>11 modals<\/li>\n<li>51 Meteor methods<\/li>\n<li>10 daemon processes<\/li>\n<\/ul>\n<p>Although Sherpa was in good technical shape to serve as the baseline for the conversion effort, the system had a lot of redundancy, primarily due to evolution. Over time, new subsystems were tacked on by cloning existing subsystems and modifying them to meet requirements. Suffice it to say that the system could benefit from some refactoring.<\/p>\n<p>My guiding philosophy for the conversion process:<\/p>\n<ul>\n<li>DRY<\/li>\n<li>Be systematic<\/li>\n<li>Don\u2019t introduce new bugs<\/li>\n<li>Capture reusable logic, patterns and techniques<\/li>\n<\/ul>\n<h3>React Components in Blaze Layouts<\/h3>\n<p>You can embed React components into Blaze templates using package <strong>react-template-helper<\/strong>. My very first React component was created and tested using this approach. Theoretically, you could use <strong>react-template-helper<\/strong> to incrementally convert an entire application into components, starting with low-level Blaze templates and then building upwards to create higher and higher level components until all templates have been systematically replaced. This approach would make it possible to keep the system functional at all times, at the expense of some additional testing and the creation of some throw-away code.<\/p>\n<p>Package\u00a0<strong>react-template-helper<\/strong> is ideal for developers experimenting with React; however, I decided against using it because I was committed to a comprehensive conversion process that would ultimately eliminate Blaze.<\/p>\n<h3>Using BlazeLayout and ReactLayout Together<\/h3>\n<p>You can use BlazeLayout and ReactLayout together in the same application. Certain routes are rendered by Blaze while others are rendered by React.<\/p>\n<p>I tried to use this approach but I encountered difficulties. Since Blaze and React render HTML into different <em>root<\/em> HTML elements, the system failed to clean up after route changes, causing defunct pages to linger in the DOM, screwing up the UI.<\/p>\n<p>Although I was able to circumvent this problem by dynamically adding\/removing CSS classes to control the visibility of pages generated, I considered this to be a hack, so I was hesitant about pushing a hybrid Blaze\/React system into production and decided against it.<\/p>\n<h3>Wholesale Conversion<\/h3>\n<p>I began performing the conversion effort on an isolated GitHub branch. I was taking my time with no hard deadline, and had no requirement to keep the system working at all times. I decided to abandon the incremental approach and began converting entire subsystems all at once.<\/p>\n<h3>My First Subsystem: Events<\/h3>\n<p>I began my conversion effort with a small subsystem called <em>Events, <\/em>an administrative page that displays a list of event records:<\/p>\n<p><a href=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/events.png\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-3196\" src=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/events.png\" alt=\"\" width=\"1277\" height=\"933\" srcset=\"https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/events.png 1277w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/events-300x219.png 300w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/events-768x561.png 768w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/events-1030x753.png 1030w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/events-705x515.png 705w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/events-450x329.png 450w\" sizes=\"(max-width: 1277px) 100vw, 1277px\" \/><\/a><\/p>\n<p>I printed a screen shot of the Events page and used a pen to circle elements in order to plan my component hierarchy. I initially came up with this component outline:<\/p>\n<blockquote><p><code>Top bar<br \/>\nBurger button<br \/>\nSystem logo<br \/>\nSystem name<br \/>\nSubsystem status indicator (many)<br \/>\nProfile photo<br \/>\nEvent selection criteria row<br \/>\nEvent type selector<br \/>\nRows selector<br \/>\nDate selector<br \/>\nEvent display body<br \/>\nEvent display row (many)<br \/>\nEvent display cell (many)<\/code><\/p><\/blockquote>\n<p>I quickly realized that the Events subsystem would require more effort than I had anticipated. There would be extra work to create universal shared layout components, such as the top bar, plus the off-canvas navigation panel fly-out that appears when the user touches the burger button. I decided to work on these shared layout components first.<\/p>\n<p>The need to support layouts led me to Arunoda\u2019s React Mounter (NPM <strong>react-mounter<\/strong>), which is ideal for converting a typical Blaze application that uses layout templates. React Mounter allows you to mount a hierarchy of components by specifying a <em>layout<\/em> component plus a <em>content<\/em> component. React Mounter does everything necessary to mount the layout component as a container with the content component serving as the body.<\/p>\n<p>I added <strong>react-mounter<\/strong> to my tech stack and began experimenting with it. The package worked beautifully, and I wholeheartedly recommend it for any application that uses reusable layouts.<\/p>\n<h3>Components<\/h3>\n<p>From a developer\u2019s perspective, a React-rendered browser page can be seen as a hierarchy of components. The entire browser page is typically represented by a top-level container component that includes many nested components; together, these components represent the page that the user sees.<\/p>\n<p>A component is declared as a JavaScript class. Ideally, each component should be declared in its own JSX file to facilitate easy reuse.<\/p>\n<p>Using JavaScript <strong>import<\/strong> statements, parent components can reference to child components, effectively declaring a hierarchy.<\/p>\n<p>At run time, when the user chooses a route, <strong>FlowRouter<\/strong> will <em>mount<\/em> (i.e., instantiate) the top-level component of that route. After invoking several life-cycle methods, the React mounter will start a process that causes the components to build the HTML page.<\/p>\n<p>Each component must have a\u00a0<strong>render <\/strong>method, which is written by the developer. The <strong>render<\/strong> method must return either raw HTML, one or more child components, or a combination of the two.<\/p>\n<p>When React renders a top-level component, it will indirectly render referenced subcomponents. React renders the hierarchy of components in a carefully choreographed dance, resulting in the HTML page that the user ultimately sees.<\/p>\n<p>Components render HTML in accord with their <em>properties\u00a0<\/em>which play the role of the <em>model <\/em>in the MVC design pattern. In a well-structured React application, the HTML rendered by a component is exclusively a function of the <strong>render<\/strong> method and the properties supplied to the component. Properties are the model, the component is the controller, and the rendered HTML is the view.<\/p>\n<p>After the initial rendering, React keeps all component instances in memory. Those component instances play an ongoing role: any changes to properties will cause the component to dynamically re-render its respective HTML. This is how React facilitates <em>reactive<\/em> behavior which is familiar to Meteor developers.<\/p>\n<p>React offers two ways to declare components: <em>ES6 classes<\/em> or <em>functions<\/em>. Moreover, rendering logic can be expressed as either JSX or straight JavaScript. I chose to use ES6 classes as JSX, which is currently the preferred way to use React.<\/p>\n<p>Seasoned React developers segregate their components into two broad categories: <em>higher-order components <\/em>(HOCs) and <em>components<\/em>:<\/p>\n<ul>\n<li>HOCs are <em>container<\/em> components that handle data fetching and subscriptions<\/li>\n<li>Components render HTML in accord with properties<\/li>\n<\/ul>\n<p>In a Meteor\/React application, HOCs set up subscriptions and fetch data. A typical Meteor HOC will have only a single method\u00a0<strong>withTracker<\/strong>\u00a0that fetches data from various sources and uses it to build a properties object. The HOC supplies the properties object to a lower-level component (henceforth <em>subcomponent<\/em>).<\/p>\n<p>It is best practice to have the HOC and its subcomponent share the same name, except the HOC name will have the suffix <strong>Container<\/strong> appended. Example:<\/p>\n<ul>\n<li><strong>EventsTableContainer<\/strong> \u2013 HOC that fetches data and supplies properties to <strong>EventsTable<\/strong><\/li>\n<li><strong>EventsTable<\/strong> \u2013 subcomponent that receives properties from HOC <strong>EventsTableContainer<\/strong><\/li>\n<\/ul>\n<p>While an HOC is tightly coupled with one subcomponent, a given subcomponent may receive properties from many different HOCs. This ability to reuse a single component from different HOCs is an important technique for maximizing component reuse.<\/p>\n<p>HOCs do the heavy lifting in a Meteor\/React application. They can be somewhat difficult to code and test because they deal with database queries and because method <strong>withTracker<\/strong> is <em>reactive<\/em>. Updates to any database collections referenced within <strong>withTracker<\/strong> will cause <strong>withTracker<\/strong> to be automatically re-executed, causing the HOCs subcomponent to be re-rendered. This occurs because <strong>withTracker<\/strong> serves as <em>both<\/em> an imperative function <em>and<\/em> an event listener. This crazy behavior will be hauntingly familiar to seasoned Meteor developers<em>, <\/em>whom are nodding their heads in agreement as they read this. React improves on Blaze by encapsulating this tricky logic inside HOCs. This encapsulation makes it easier to pinpoint problems, ultimately reducing cost.<\/p>\n<p>A well-written subcomponent will never directly invoke a MongoDB fetch operation, but will instead render HTML in accord with supplied properties. Subcomponent rendering logic should refer only to neutral component properties, which can be seen as the\u00a0<em>source of truth<\/em>\u00a0governing all rendering decisions.<\/p>\n<p>Dividing responsibilities between HOCs and normal components is an excellent way to separate concerns. Many React developers enthusiastically support this approach, because it clearly separates the concern of data fetching from the concern of rendering. This approach also makes normal components easier to reuse.\u00a0 As long as an HOC can be written to supply the expected properties, the normal component can function properly in any usage scenario.<\/p>\n<h3>Declaring HOCs<\/h3>\n<p>A Meteor\/React application will consist of a mixture of HOCs and normal components. There are two approaches to using HOCs:<\/p>\n<ol>\n<li>Declare a single top-level HOC that fetches data, then passes that data as properties down through the hierarchy of subcomponents.<\/li>\n<li>Declare many narrowly-focused HOCs at various levels in the component hierarchy.<\/li>\n<\/ol>\n<p>While there is no right or wrong answer, I personally tend to use many narrowly-focused HOCs. This minimizes property handling by keeping HOCs close to subcomponents that they feed.<\/p>\n<h3>Granularity<\/h3>\n<p>You will need to decide on the proper\u00a0<em>granularity<\/em> of your components. While there are no hard-and-fast rules, I&#8217;ve discovered a technique that you may find helpful.<\/p>\n<p>If you notice repeating patterns of HTML in your <strong>render<\/strong> method, you can eliminate redundancy by breaking out the repeating HTML into a reusable component. If you DRY, you will create more components than you had expected. I had guessed that Sherpa would require a few dozen components, but by the time the conversion effort was completed, I had authored 64 HOCs and 168 normal components, a total of 232 components.<\/p>\n<p>By the way, early in the project, I struggled with a kind of psychological aversion to creating new components. I was primarily concerned about performance. Yet, after some experimentation, I found my performance concerns were unfounded. I was seeing excellent performance, better than Blaze in most instances, even in subsystems with hundreds of components.<\/p>\n<p>Once my concerns had been put to rest,\u00a0I proceeded to create components with wild abandon.\u00a0Now, I can create new components effortlessly, and surprisingly, those components will often work correctly the first time without debugging.<\/p>\n<h3>Naming Conventions<\/h3>\n<p>When you create a large number of components, standards and naming conventions become super important. Here are some standards that I choose to abide by:<\/p>\n<ul>\n<li>One HOC or normal component per file<\/li>\n<li>Component name makes sense and is self-documenting<\/li>\n<li>Components are stored in different folders broken down by subsystem<\/li>\n<li>Component name matches file name<\/li>\n<li>HOCs always end with <strong>Container<\/strong><\/li>\n<\/ul>\n<p>In many cases, there will be a one-to-one correspondence between an HOC and its contained component. However, it will occasionally be advantageous to define multiple HOCs that wrap the <em>same<\/em> component. Consider this example:<\/p>\n<table>\n<tbody>\n<tr>\n<td width=\"319\"><strong>HOC<\/strong><\/td>\n<td width=\"319\"><strong>Wrapped Component<\/strong><\/td>\n<\/tr>\n<tr>\n<td width=\"319\">OffCanvasNavStandardContainer<\/td>\n<td width=\"319\">OffCanvasNav<\/td>\n<\/tr>\n<tr>\n<td width=\"319\">OffCanvasNavDiagContainer<\/td>\n<td width=\"319\">OffCanvasNav<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Both <strong>OffCanvasNavStandardContainer<\/strong> and <strong>OffCanvasNavDiagContainer<\/strong> serve as wrappers for <strong>OffCanvasNav<\/strong>. Each HOC may have completely different subscriptions and different rules for fetching data. This is absolutely fine, as long as both HOCs supply properties as necessary to meet the requirements of subcomponent <strong>OffCanvasNav<\/strong>.<\/p>\n<h3>Blaze Data Contexts Versus React Properties<\/h3>\n<p>Behind every Blaze template there exists a tree of objects called the <em>data context<\/em>. Under Blaze, the data context plays the role of model in the MVC design pattern.<\/p>\n<p>Blaze provides the developer with several options for setting the data context of a Blaze template, but by far the most common approach is to allow Iron Router to provide it. For each route path, Iron Router fetches and returns a tree of objects which comprise the data context of that route. Instructions for retrieving data context objects must be expressed by the developer in the Iron Router <strong>data<\/strong> function, which is part of each route descriptor. Iron Router provides the data context to Blaze so that templates can refer to it during HTML rendering.<\/p>\n<p>Meteor developers tend to complain about the way Iron Router deals with data contexts. Hard-core Meteor insiders characterize it as an <em>anti-pattern<\/em>, and they have devised clever ways to improve the situation. One solution is to remove Iron Router from the system, add FlowRouter, and then use template-level subscriptions. This approach is recommended by many Meteor gurus, and I agree with them wholeheartedly. But for clarity, I will compare the design of a typical, clunky Blaze application, in which Iron Router establishes data contexts, with a modern React application.<\/p>\n<p>One problem with Blaze is that it treats the data context as the <em>sole<\/em> input into the template rendering process. There is no <em>easy<\/em> way to configure a template to appear or behave differently in different usage scenarios. For example, consider an ostensibly-reusable template that renders a user list into HTML. In some scenarios, the user list may need to be selectable, while in other scenarios, selection of the user list must be inhibited. In order for the user list template to be easily reusable, there should be a convenient way for the developer to configure the template to behave differently in different scenarios. Ideally such configuration settings would be supplied by the parent (i.e., calling) template.<\/p>\n<p>Another problem with a typical Blaze application is that data contexts are <em>monolithic;<\/em> there is only a single tree of data for each route, and that tree must contain the union set of <em>all<\/em> data needed by <em>all<\/em> templates used within that route.<\/p>\n<p>This monolithic data context makes it hard to write reusable templates. Clearly, templates must know the names of fields in the data context, but they must also be intimately aware of the <em>structure<\/em> of the data context in order to successfully navigate via Blaze functions such as Template.currentData and Template.parentData. These navigational functions are hard-coded into the supposedly-reusable template. Consequently, a reusable template becomes\u00a0<em>tightly coupled<\/em> with a specific data context. Attempting to reuse that template elsewhere will require coding changes to replicate those portions of the data context required by the reusable template. The effort to keep data contexts in sync with ongoing enhancements to templates makes Blaze programming more tedious than need be.<\/p>\n<p>React eliminates this problem by allowing each component to explicitly declare its information needs (i.e., properties), tantamount to a schema. At runtime, HOCs perform retrieval functions and then supply data as properties to lower-level components. If those properties fail to satisfy the declared requirements of lower-level component(s), the application will fail with a clear explanation logged to the developer console. Such problems can be fixed very quickly at low cost.<\/p>\n<p>In contrast to a legacy Blaze application with monolithic data context, a React route can have <em>many<\/em>\u00a0HOCs at different levels, tantamount to having\u00a0<em>many<\/em> data contexts. This means React applications tend to have better <em>encapsulation<\/em> than Blaze applications. Better encapsulation makes it easier to develop reusable component that work predictably and reliably in different usage contexts, even if those components require complex configuration settings.<\/p>\n<p>Of course, there is no free lunch, and React components take more time to develop than standard Blaze templates.<\/p>\n<p>The bottom line is that Blaze allows developers to cut corners in a way that seems perfect for churning out proof-of-concept MVPs while React engenders industrial-strength discipline that screams <em>this is enterprise software.<\/em><\/p>\n<h3>VXFrame<\/h3>\n<p>Near the end of the Sherpa conversion effort, I formally separated the reusable components and subsystems from their application-specific counterparts by splitting the code into two separate GitHub repositories:<\/p>\n<ul>\n<li>Sherpa &#8211; application-specific components and code<\/li>\n<li>VXFrame &#8211; reusable components and code<\/li>\n<\/ul>\n<p>As the conversion effort entered its final phases, I started testing VXFrame independently of Sherpa to ensure that it would function properly as a stand-alone project. In this process, I ended up fixing many longstanding issues and improved code organization.<\/p>\n<p>When the conversion effort was finally complete, VXFrame had many key features that you\u2019ll find in a typical multi-tenant SaaS including:<\/p>\n<ul>\n<li>Configurable appearance<\/li>\n<li>Responsive and touch-friendly UI that works well on all devices<\/li>\n<li>Curated third-party packages that work well together<\/li>\n<li>Flexbox-based layout system<\/li>\n<li>Reusable layouts including top bar and off-canvas navigation<\/li>\n<li>Animations that leverage hardware-assisted 3D translations<\/li>\n<li>Inventory of React-based input widgets<\/li>\n<li>Data forms with two-way data binding to MongoDB<\/li>\n<li>Rule-based validation and formatting (client-side and server-side)<\/li>\n<li>Dynamic or traditional database updates<\/li>\n<li>Declared database schema via Collection2<\/li>\n<li>Security-conscious allow\/deny rules<\/li>\n<li>Parameterized event notifications via email (Mailgun) or SMS (Twilio)<\/li>\n<li>Multi-tenant database design, partitioned into tenants and domains<\/li>\n<li>User &amp; Domains management subsystem<\/li>\n<li>User Profile subsystem<\/li>\n<li>Infrastructure for monitoring and managing status of external systems<\/li>\n<li>Internationalization<\/li>\n<li>Performance management<\/li>\n<li>Secure architecture<\/li>\n<li>Logging subsystem<\/li>\n<li>Build and push scripts to facilitate frequent code changes<\/li>\n<li>Scalable via Nginx clustering and MongoDB replication<\/li>\n<\/ul>\n<p>In addition to reusable code, VXFrame provides <em>examples<\/em>, <em>patterns<\/em> and <em>programming conventions<\/em> that can make it easier to develop new application-specific subsystems.<\/p>\n<p>Since React and Meteor are super-flexible, the onus is on the developer to decide how to implement required features and to select the best third-party packages. Ironically, this flexibility can make it difficult to choose the best path forward. For better or worse, VXFrame is opinionated, and it offers clear patterns for implementing new subsystems.<\/p>\n<p>VXFrame can reduce costs by reducing third-party package research. It includes dozens of third-party packages that are proven to work well together. New application-specific subsystems can be created at low cost by cloning VXFrame subsystems, then modifying them to suit. Alternatively, in cases where a proposed subsystem bears no resemblance to any existing VXFrame subsystem, it is possible to add third-party packages and to code new subsystems from scratch, while still leveraging VXFrame services and patterns.<\/p>\n<p>VXFrame is intentionally\u00a0<em>layered<\/em>\u00a0to allow developers to override default behaviors or subsystems with application-specific logic. If default behaviors are acceptable, the application-specific <em>customization layer<\/em> can be minimized.<\/p>\n<h3>LESS\/CSS Rules<\/h3>\n<p>VXFrame effectively reconciles Bootstrap LESS\/CSS rules with a reusable React component architecture.<\/p>\n<p>Many internet postings describe the component-centric advantages of React without taking any strong positions on styling and layout. Some React developers eschew CSS rules entirely, opting instead for programmatically-controlled styles embedded within the components. VXFrame fully embraces Bootstrap and avoids embedded styling.<\/p>\n<p>Given the decision to use Bootstrap, it was necessary to devise standards and conventions to allow VXFrame components to work harmoniously with Bootstrap CSS classes. Each VXFrame component declares a set of standard CSS classes via React default properties. VXFrame components declare good defaults that facilitate typical layout behaviors and responsive design, so that in most cases the developer can ignore layout issues. When the developer needs finer-grained control, default CSS classes can be overridden via component properties.<\/p>\n<h3>Flexbox<\/h3>\n<p>VXFrame uses Flexbox (exclusively) for controlling component layout.\u00a0 Flexbox is a relatively new W3C standard which is now supported by all major browsers.<\/p>\n<p>Flexbox rules allow developers to declare divisions of HTML that are either fixed in size or that may grow to fill available space, automatically adjusting to any device. Flexbox rules are indispensable for creating single-page apps that behave like native apps.<\/p>\n<p>VXFrame replaces quirky Bootstrap grid <strong>float<\/strong> CSS rules with functionally-equivalent Flexbox declarations. As a result, the system is able to render complex single-page layouts declaratively, minimizing browser-specific layout anomalies.<\/p>\n<h3>Layouts<\/h3>\n<p>VXFrame includes several built-in layout components:<\/p>\n<ul>\n<li>LayoutStandard \u2013 Standard layout including top bar and off-canvas navigation controls<\/li>\n<li>LayoutDiag \u2013 Diagnostic layout for Events and System Log subsystems<\/li>\n<li>LayoutNone \u2013 Null layout that can be used to create free-form pages from scratch<\/li>\n<\/ul>\n<p>Most VXFrame subsystems use LayoutStandard, which includes a top bar and off-canvas navigation that appears when the user presses the \u201cburger\u201d button. The off-canvas navigation fly-out uses hardware-accelerated 3D translations for smooth performance on any client, including older phones and tablets.<\/p>\n<p>Many applications can be developed using built-in layouts. Additional layout components can be added if necessary.<\/p>\n<h3>Animation<\/h3>\n<p>LayoutStandard is a generalized container that houses a developer-supplied subcomponent, henceforth referred to as the <em>content<\/em>. This layout encapsulates the content inside a <em>transition group<\/em> to permit changes in content to be animated. VXFrame built-in animations include cross-fades, and left-to-right or right-to-left slides which are particularly important on hand-held devices.<\/p>\n<h3>Forms<\/h3>\n<p>VXFrame includes comprehensive support for <em>forms<\/em>, making it easy to develop subsystems that gather information from users and store it in the database.<\/p>\n<p>VXForm is component that can contain any number of input controls such as:<\/p>\n<ul>\n<li>VXButton &#8211; Button control with optional loading spinner<\/li>\n<li>VXCell \u2013 Content-editable input cell used for tabular data<\/li>\n<li>VXCheck \u2013 Checkbox control<\/li>\n<li>VXDate \u2013 Bootstrap-styled date picker<\/li>\n<li>VXFieldBox \u2013 Read-only box for displaying a field of data<\/li>\n<li>VXFieldSet \u2013 Group box or field set<\/li>\n<li>VXImage \u2013 Image picker for photos or icons<\/li>\n<li>VXInput \u2013 Standard input<\/li>\n<li>VXModal \u2013 Modal dialog<\/li>\n<li>VXMultiSelect \u2013 Control for selecting one or many values with checkboxes<\/li>\n<li>VXSelect \u2013 Standard select for drop-downs or combo boxes<\/li>\n<li>VXSpin \u2013 Touch-friendly spinner to select numeric values<\/li>\n<li>VXSwitch \u2013 Bootstrap-styled switch<\/li>\n<li>VXTabFolder \u2013 Container of Bootstrap user-selectable tabs<\/li>\n<li>VXTab \u2013 Single tab contained within VXTabFolder<\/li>\n<li>VXTextArea \u2013 Text Area control<\/li>\n<\/ul>\n<p>VXForm provides many features that are typical of web forms:<\/p>\n<ul>\n<li>Validation rules for input controls (both client and server)<\/li>\n<li>Integration with Bootstrap error handling CSS classes<\/li>\n<li>Required fields support<\/li>\n<li>Localized popovers that explain simple validation issues<\/li>\n<li>Push notifications to explain complex validation issues or to show deferred results<\/li>\n<li>Field formatting rules<\/li>\n<li>Data binding to MongoDB (both dynamic and traditional save\/cancel updates)<\/li>\n<li>Custom fetch and update handlers<\/li>\n<li>Custom event handlers<\/li>\n<li>Field labels, tooltips and placeholders<\/li>\n<\/ul>\n<p>VXForm and its nested input components declare properties to control the validation and update process.<\/p>\n<p>VXForm specifies global settings such as:<\/p>\n<ul>\n<li>MongoDB collection to be updated<\/li>\n<li>MongoDB ID of the record to be updated<\/li>\n<li>Dynamic (true\/false) which control <em>when<\/em> database updates will occur<\/li>\n<\/ul>\n<p>Nested input components specify additional properties that vary based on component type; for example, a VXInput control declares additional settings such as:<\/p>\n<ul>\n<li>MongoDB field name to be updated<\/li>\n<li>Field label and optional tooltip and placeholder<\/li>\n<li>Validation and formatting rules<\/li>\n<li>Custom fetch and update handlers<\/li>\n<li>Binding type (i.e., data type)<\/li>\n<\/ul>\n<p>The VXFrame validation subsystem is event-driven so processing occurs whenever the user changes the value of an input component. Formatting and validation occurs immediately, while update processing can occur in two ways:<\/p>\n<ul>\n<li><em>Traditional<\/em> &#8211; Database is updated when the user presses a designated button (e.g., Save)<\/li>\n<li><em>Dynamic<\/em> &#8211; Database is updated immediately whenever an input control value changes<\/li>\n<\/ul>\n<p>Traditional validation permits the emulation of legacy-style web forms, where information is gathered and then sent to the server when a button is pressed. In this mode, the user is typically offered two buttons at the bottom of the form: Save and Cancel. Save will cause the system to update the database, while Cancel will restore the form to its original state, abandoning any in-progress inputs.<\/p>\n<p>Dynamic validation causes the system to immediately store information into the database whenever the user changes the value of an input control. This mode is ideal for collaborative applications where data needs to be dynamically shared between users.<\/p>\n<h3>Component State<\/h3>\n<p>VXFrame input components automatically manage their own states, so there is no need to <em>elevate<\/em> state to higher level components. Each component maintains its <em>internal<\/em> value (i.e., state) as an appropriate JavaScript type which varies depending on the type of control. For example, VXDate components maintain the selected date as JavaScript type Date.<\/p>\n<p>When a component is mounted, the component state value is <em>rendered<\/em> according to <em>formatting<\/em> rules.\u00a0 For example, a telephone number field may be stored internally as a string of digits, yet rendered using a mask consisting of parenthesis surrounding the area code and a dash between the prefix and suffix. When data is entered by the user, mask characters are automatically <em>stripped<\/em> from the input as specified in the formatting rules.<\/p>\n<p>In order to function properly within the VXForm container, each input component implements a variety of standardized methods including:<\/p>\n<ul>\n<li><strong>setValue<\/strong> sets the internal value of the component<\/li>\n<li><strong>getValue<\/strong> returns the internal value of the component<\/li>\n<li><strong>reset<\/strong> resets the component to its original state (captured when the component is mounted)<\/li>\n<\/ul>\n<p>VXFrame components automatically register themselves within their VXForm container when they are mounted. This registration process allows VXFrame to:<\/p>\n<ul>\n<li>Automatically determine whether all required fields have been completed<\/li>\n<li>Display validation errors and warnings using popovers and push notifications<\/li>\n<\/ul>\n<p>VXFrame allows multiple forms to peacefully coexist on a page when necessary.<\/p>\n<h3>Modals<\/h3>\n<p>VXModal components are wrappers for Bootstrap modals.<\/p>\n<p>Traditionally, Bootstrap modals have been pre-rendered into invisible HTML that lingers in the web page document body. When the application needs to display the modal, it invokes a Bootstrap function which makes the HTML visible, while simultaneously triggering the &#8220;show&#8221; animation to bring the modal into view. When the user completes the modal, the system triggers the &#8220;hide&#8221; animation and makes the HTML invisible. In contrast, VXModal HTML is dynamically created and destroyed as needed. This approach is far superior and makes it much easier to code and manage modals, reducing cost.<\/p>\n<p>A VXModal component typically contains a VXForm subcomponent, allowing the modal to automatically handle input validation and required fields processing.<\/p>\n<p>VXFrame comes with three standard modal footer components:<\/p>\n<ul>\n<li>ModalFooterSimple \u2013 single button with user-defined word to dismiss the modal (e.g., OK).<\/li>\n<li>ModalFooterYesNo \u2013 Yes and No buttons with Yes button tied to event listener and No dismissing the modal.<\/li>\n<li>ModalFooterConfirm \u2013 Confirm and Cancel buttons for important decisions, such as starting a background process or retiring a record.<\/li>\n<\/ul>\n<p>Developers can easily create additional custom modal footer components if necessary.<\/p>\n<h3>Off-Canvas Navigation<\/h3>\n<p>VXFrame comes with an off-canvas navigation bar which is normally hidden from view, but will fly in from the left side of the screen when the user touches or clicks the \u201cburger\u201d button on the top bar:<\/p>\n<p><a href=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/off-canvas-nav.png\"><img loading=\"lazy\" class=\"border-image aligncenter wp-image-3197 size-full\" src=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/off-canvas-nav.png\" alt=\"\" width=\"1280\" height=\"933\" srcset=\"https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/off-canvas-nav.png 1280w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/off-canvas-nav-300x219.png 300w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/off-canvas-nav-768x560.png 768w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/off-canvas-nav-1030x751.png 1030w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/off-canvas-nav-705x514.png 705w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/off-canvas-nav-450x328.png 450w\" sizes=\"(max-width: 1280px) 100vw, 1280px\" \/><\/a><br \/>\nVXFrame uses off-canvas navigation instead of the standard Bootstrap menu bar, primarily to conserve screen real estate.<\/p>\n<h3>Entity Lists<\/h3>\n<p>Modern applications display lists of data which can be touched, clicked, dragged and dropped. Each item in the list typically has an icon or picture on the left side, with explanatory lines of text on the right side. Example:<\/p>\n<p><a href=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-list.png\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-3194\" src=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-list.png\" alt=\"\" width=\"479\" height=\"246\" srcset=\"https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-list.png 479w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-list-300x154.png 300w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-list-450x231.png 450w\" sizes=\"(max-width: 479px) 100vw, 479px\" \/><\/a><\/p>\n<p>In VXFrame, entity lists are represented by component EntityList. An EntityList component can any number EntityItem components, each of which contains:<\/p>\n<ul>\n<li>An image<\/li>\n<li>A heading (shown larger)<\/li>\n<li>Up to two optional subheadings<\/li>\n<li>Optional chevrons for slide panels<\/li>\n<li>Optional image decoration<\/li>\n<\/ul>\n<p>Developers can \u201cwrap\u201d EntityList components to create specialize, reusable lists, effectively inheriting EntityList functions and behaviors. For example, the list of users shown above is represented by UserEntityList, a specialized EntityList component.<\/p>\n<p>EntityList and EntityItem components are highly parameterized, allowing wrapper components to specify basic appearance, images, titles, decorations and tooltips. A variety of event handlers can be registered with EntityList and EntityItem to handle gestures such as selection and drag\/drop processing.<\/p>\n<h3>Entity Panels<\/h3>\n<p>Entity panels are used to display and potentially edit information. Typically, an entity panel will display the information contained in a single database record, but it may also combine information from several related records. Example:<\/p>\n<p><a href=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-panel.png\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-3195\" src=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-panel.png\" alt=\"\" width=\"590\" height=\"497\" srcset=\"https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-panel.png 590w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-panel-300x253.png 300w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/03\/entity-panel-450x379.png 450w\" sizes=\"(max-width: 590px) 100vw, 590px\" \/><\/a><\/p>\n<p>VXFrame allows you to develop entity panels by combining reusable subcomponents. For example, the entity panel shown above is represented by component UserDomainViewRight, which is comprised of the following subcomponents:<\/p>\n<ul>\n<li>RightPanel \u2013 Outer component which dynamically fills available screen space. By default the panel appears \u201craised\u201d because of drop shadow drawn around the perimeter.<\/li>\n<li>RightHeader \u2013 A collapsible header which has a large image, a heading and up to two subheadings. Typically, RightHeader will contain a VXForm that contains fields of information of an entity.<\/li>\n<li>EntityListHeader \u2013 A thin title that is a heading for an entity list within the panel. In the example the thin rectangle bearing the word Domains is declared as an EntityListHeader component.<\/li>\n<li>DomainEntityList \u2013 The list of domains is a standard EntityList driven by the list of domains to which the user belongs. The DomainEntityList will automatically fill available space and will be scrollable by default.<\/li>\n<\/ul>\n<p>Entity panels allow the developer to quickly snap together reusable subcomponents like Lego blocks to form panels which can be used for display or edit purposes.<\/p>\n<h3>Slide Pairs<\/h3>\n<p>Modern web applications use sliding animations, typically triggered by user gestures, such as swiping left or right. VXFrame makes it easy to develop subsystems that boast smooth, hardware-assisted sliding animations.<\/p>\n<p>Three VXFrame components work together to deliver sliding behaviors:<\/p>\n<ul>\n<li>SlidePairContainer<\/li>\n<li>SlidePair<\/li>\n<li>SlidePanel<\/li>\n<\/ul>\n<p>These components have been carefully designed to work together seamlessly to deliver an optimal experience on any desktop system or hand-held device.<\/p>\n<p>On desktop systems, these three components create a side-by-side display, where the left-hand side presents a list of entities, and the right hand side presents the details of the currently-selected entity:<\/p>\n<p><a href=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-both.png\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-3234\" src=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-both.png\" alt=\"\" width=\"1030\" height=\"775\" srcset=\"https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-both.png 1030w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-both-300x226.png 300w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-both-768x578.png 768w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-both-705x530.png 705w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-both-450x339.png 450w\" sizes=\"(max-width: 1030px) 100vw, 1030px\" \/><\/a><\/p>\n<p>The utility of SlidePair is evident on a smaller screen such as a phone; in this case, SlidePair will display only the left-side panel:<\/p>\n<p><a href=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-left.png\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-3236\" src=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-left.png\" alt=\"\" width=\"425\" height=\"743\" srcset=\"https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-left.png 425w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-left-172x300.png 172w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-left-403x705.png 403w\" sizes=\"(max-width: 425px) 100vw, 425px\" \/><\/a><\/p>\n<p>When the user touches any of the users in the list, the SlidePair will trigger a right-to-left slide animation to bring the user detail panel into view:<\/p>\n<p><a href=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-right.png\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-3235\" src=\"http:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-right.png\" alt=\"\" width=\"424\" height=\"744\" srcset=\"https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-right.png 424w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-right-171x300.png 171w, https:\/\/sotaenterprises.com\/wp-content\/uploads\/2018\/04\/users-domains-right-402x705.png 402w\" sizes=\"(max-width: 424px) 100vw, 424px\" \/><\/a><\/p>\n<p>The user can then perform various functions on the detail panel, such as editing, cloning or deleting the user record.<\/p>\n<p>If the user presses the <em>back button<\/em> (<strong>&lt; Users<\/strong>), SlidePair will trigger a left-to-right animation to return to the user list panel. SlidePair maintains a <em>return<\/em> <em>stack<\/em> so that sliding behaviors can be nested to any number of levels.<\/p>\n<p>To use SlidePair, the developer must create <em>left-side<\/em> and <em>right-side<\/em> components.\u00a0 SlidePair subcomponents follow well-defined patterns, invoking pre-written methods in key event listeners. Typical animation behaviors can be achieved without custom programming.<\/p>\n<p>SlidePair alleviates much of the drudgery normally needed to implement responsive applications. Developers are free to develop and refine their left-side and right-side components without having to deal directly with complex animation mechanics.<\/p>\n<h3>Core Subsystems<\/h3>\n<p>VXFrame comes with <em>core subsystems<\/em> that are essential for most multi-tenant SaaS applications. Subsystems may be used as-is or extended as needed.<\/p>\n<table>\n<tbody>\n<tr>\n<td width=\"319\"><strong>Subsystem<\/strong><\/td>\n<td width=\"319\"><strong>Description<\/strong><\/td>\n<\/tr>\n<tr>\n<td width=\"319\">Signin<\/td>\n<td width=\"319\">Provides a sign-in landing page bearing system logo and name, plus entry fields for username and password. Provides hyperlink to recover lost passwords.<\/td>\n<\/tr>\n<tr>\n<td width=\"319\">Profile<\/td>\n<td width=\"319\">Allows users to update their user profiles, including basic profile data, locale, language preference, timezone, name\/address, phone numbers, photo image upload, password reset functions, notification settings, preferences and scheduled reports.<\/td>\n<\/tr>\n<tr>\n<td width=\"319\">Tenants<\/td>\n<td width=\"319\">Allows users to edit basic tenant-level information, such as an icon that represents the tenant. Also allows general users to navigate to their tenants and domains and to select a domain to <strong>Make Current<\/strong> (i.e., switch into domain).<\/td>\n<\/tr>\n<tr>\n<td width=\"319\">Users &amp; Domains<\/td>\n<td width=\"319\">Manages users and domains, including functions for creating new users, creating new domains and managing the relationships between users and domains via drag\/drop. Includes multi-tenant security model that permits users to have different privileges (roles) with respect to different domains.\u00a0Includes administrative functions for enrolling new users and resetting passwords.<\/td>\n<\/tr>\n<tr>\n<td width=\"319\">System Settings<\/td>\n<td width=\"319\">Administrative subsystem available to authorized users that permits updates to system, tenant and domain settings, including credentials for external systems such as Mailgun and Twilio.<\/td>\n<\/tr>\n<tr>\n<td width=\"319\">Templates<\/td>\n<td width=\"319\">Generalized subsystem for creating, updating and retiring email templates, and components for setting emails to users, including mail-merge-style functions for performing variable substitutions. System includes support for sending HTML emails using templates.<\/td>\n<\/tr>\n<tr>\n<td width=\"319\">Events<\/td>\n<td width=\"319\">Generalized subsystem for displaying system events, typically available to super administrators only.<\/td>\n<\/tr>\n<tr>\n<td width=\"319\">System Log<\/td>\n<td width=\"319\">Generalized subsystem that consolidates client-side and server-side messages into a single display of rolling messages, typically available to super administrators only.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Cost<\/h3>\n<p>Does React make sense from a strictly cost\/benefits perspective? The short answer is &#8220;it depends&#8221; so let&#8217;s take it one case at a time.<\/p>\n<p>When developing a new multi-tenant SaaS application from scratch, I\u2019m sold on the cost effectiveness of React, both for up-front development and ongoing maintenance. The only drawback is the steep learning curve, but this can be mitigated by leveraging a reusable framework.<\/p>\n<p>When developing from scratch <em>without<\/em> a reusable framework, React makes sense for larger systems that are subject to frequent coding changes by a team of developers, particularly mission-critical systems where UI bugs have severe consequences. In such cases, React coding is economically feasible, because the up-front costs to create reusable components can be offset by lower maintenance costs in the future.<\/p>\n<p>When converting an existing system from Blaze to React, if your goal is to completely componentize the front end, you will likely reduce cost by leveraging a reusable framework. Instead of reinventing the wheel, you can can give your application a front-end makeover while preserving your back-end business logic.<\/p>\n<p>Please be aware that adding React to your technology stack will be only the first step in a long process. React &#8220;conversion&#8221; can mean practically anything, from cursory experimentation with a few reusable components to a total replacement of the UI, culminating with the removal of Blaze from the system.<\/p>\n<p>One way to measure the success of a Blaze-to-React conversion effort is to quantify the percentage of the document object model (DOM) that is controlled by React <em>after<\/em> conversion. In an ideal React application, the DOM will be completely controlled by React, and jQuery direct modifications of the DOM are considered an anti-pattern. React\u2019s purpose is to facilitate a simplified, component-based programming experience that insulates the developer from the complexities of HTML, the DOM and jQuery. Code that bypasses the React <em>component abstraction<\/em> goes against this ideal and may increase maintenance costs and the probability of defects.\u00a0 However, even in an ideal project, parts of an application may never be componentized, particularly third-party packages that are not yet React-ready. Attempting to achieve 100% componentization can be costly and will usually reach a point of diminishing returns.<\/p>\n<p>Note that component granularity alone is no panacea. A developer in a rush to finish converting a subsystem might create myriad granular components that have no chance of being reused. Creating truly reusable components requires more effort and than narrowly satisfying the immediate requirements at hand.<\/p>\n<p>One helpful technique to engender reuse is to routinely test components\u00a0<em>outside<\/em> of the system for which they were originally developed. This extra testing increases cost but ensures that the components are truly reusable.<\/p>\n<p>Perhaps the most important key to a successful Blaze-to-React conversion is to accept that the process will require more time and effort than one would hope. You should strive to:<\/p>\n<ul>\n<li>Convert the existing application from Blaze to React in a non-disruptive way<\/li>\n<li>Capture a set of reusable components that can reduce costs of future efforts<\/li>\n<\/ul>\n<p>The secondary goal of capturing reusable components can help justify conversion costs, because those components will reduce the costs of future projects.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In early 2017, I decided to convert one of my client\u2019s applications from Blaze to React. This is the story of that conversion effort. If you are a Meteor developer, this post will likely be interesting to you, particularly if you are considering moving to React. For ordinary folks, I\u2019ve done my best to minimize [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0},"categories":[1],"tags":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/posts\/3181"}],"collection":[{"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3181"}],"version-history":[{"count":83,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/posts\/3181\/revisions"}],"predecessor-version":[{"id":3302,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=\/wp\/v2\/posts\/3181\/revisions\/3302"}],"wp:attachment":[{"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3181"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3181"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sotaenterprises.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3181"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}