Ignition Community Live with Kent Melville
Intro to Dynamic Design56 min video / 42 minute read
A look at how Ignition is designed to be object oriented and dynamic from the ground up. We will be reviewing UDTs, parameterized views (embedded, repeated, and on a canvas), styles, themes, dashboards, parameterized URLs, bindings, and scripts.
Kent: Welcome, everyone. My name is Kent Melville, sales engineering manager here at Inductive Automation, and this is Ignition Community Live. Today's topic is “Intro to Dynamic Design,” and that's me, in case we haven't met. Hopefully we're gonna meet all of you at some point. This year's ICC is just virtual, but we're excited when things get back to normal, and we can have everybody out to Folsom, California again, would love to talk with you. But today, going into this concept of dynamic design, we're gonna be looking at how Ignition is designed to be object-oriented and dynamic from the ground up, and we'll be reviewing UDTs, parameterized views. Within parameterized views, we're gonna be talking about embedded views, repeated views, and views on a canvas. I'll also be talking about styles, themes, dashboards, parameterized URLs, binding scripts, all kinds of stuff.
Kent: It should be an interesting hour today. We're gonna start at the basics, and then we'll get more advanced by the end, so there should be a little something for everyone today. But what is dynamic design? I talked about it in that description, but essentially dynamic design to me is starting with a plan of how you want your project to be structured. And then once you've got the plan, the plan is key, you gotta start with a plan, and then you create your project that it's gonna use simple building blocks which will then be pieced together to make your more complex screens and your whole project.
Kent: And so that's where the object-oriented design comes in. You're gonna create objects on the data side, you're gonna create objects on the visualization side, you're gonna create objects through URLs, in some ways, you're gonna be doing all this so that you have all the pieces and you just put them together in different configurations to make up your project. And then the other part of dynamic design is really building a project so that it can be scaled easily. You can build it for a small perfect concept, and then roll that out to a site, and then maybe to a bunch of sites across an enterprise. Really, that's the goal here, is to talk about how you can use these building blocks to be reusable so that you can scale up as needed.
Kent: It's worth noting here, I'm going through several slides... I'm gonna go through the slides pretty quickly 'cause I'm an engineer, and so this is the boring part to me, and then we're gonna jump in and get to a project that I built to show off some of these dynamic designs. And so we're gonna get into the Designer here in just a few minutes.
Kent: So how do I implement dynamic design? Once again, start with a plan. For my case, I was gonna build out a project to show you guys, and so I thought, "Well, what's my plan?" And so I decided that I was going to create a multi-site architecture, but that I was gonna do it all from a single Ignition Gateway for ease of demo-ing for you guys today. I decided that I was gonna use some simulators that have objects in them, and I was gonna create some things based on those objects, and then there'd be similar objects at each of the sites, and so then I could show creating something for one site and have it rolled out to multiple sites. That was my plan for this.
Kent: And going into more detail on that plan, I needed to build out a data model especially using UDTs, which we'll talk more about where possible. And then I wanted to create a parent project that could be inherited by future projects for each site. I wanted to make everything inside that parent project parameterized views so that they could be reused. They would be my basic building blocks. Often your parameterized views will be things like, if you have a motor UDT, you're gonna have a motor parameterized view, or if you have a device, like you see I have, it's gonna have device UDT would match to a device parameterized view, and so on. Then after you've created all of these parameterized views inside a parent project, then you go and create the actual projects inheriting all of those parameterized views.
Kent: To talk about all of these things in a little more detail starts with the data model. Like I said, for me, I looked and I said, "All right, I'm gonna be using some simulated devices, and inside each of these devices, there's gonna be several motors." And so I went ahead and created a UDT for a motor, and it has an Amps tag, it has an HOA tag, and I added a memory tag for Motor Name. Inside my devices, I said there are seven motors, and so I have seven instances of my motor UDT inside a device, and so you can have a parent-child relationship inside these UDTs. But what is a UDT? It's really just a collection of tags, and those tags can be OPC tags, it can be memory tags, expression tags, or other UDTs like you see with the motors there.
Kent: What's nice about UDTs is that you define a definition. You can see all of these are inside my Data Types folder, these are just definitions. And when I create them, I can add alarms or scripts or any other configuration on my tags, and those are gonna be shared once I create all of these instances of UDT. We created the definitions here, I can now create instances, and those instances are tied back to the definition, so that if I add an alarm on the definition, all of my instances inherit that alarm automatically. If I create a script, once again, it's inherited. This is the first building block, so to speak, this is the first object-oriented design we're doing, is we create objects for our data model, so in my case, I have motors and I have devices.
Kent: You can have as many instances as you like, and the way that the instances differ from each other, is that you pass parameters to them, you're gonna see that's very common today. I'm probably gonna talk about parameterized instances, parameterized views, all these things, but essentially, when you create this object that's reusable, each time you use it, you want it to be pulling different sets of data, and so you parameterize it, pass in these different values so that you get its unique data associated with it.
Kent: The next thing I did, once I had my data model, I went and created a parent project in mine. I called it a Site Template. And this is where I'm putting all those parameterized views. It's also where I'm putting styles, I can put named queries, any other shared resources that I want to create inside my projects. After that, I can go ahead and... Well, before I jump into the actual projects, the parent project is also really key because now, when I go to create Site 1 or Site 2 or Site 3, these other projects, when they inherit my site template, they're gonna have access to all of those resources, they can overwrite those resources where needed, so you're not stuck with only using it as it's defined in the template, but you can also customize it. And it's great because a parent project works on a single Gateway, but you can also move that parent project to other sites in an enterprise architecture. And all those different sites could inherit these same templates, so you maintain consistency and standards between all of your sites And so, parent projects are huge.
Kent: It's also worth noting that, if you do have multi-site architectures, our Enterprise Administration Module allows you to move project resources around or entire projects around, and that could be really helpful for... If you make a change to your parent project, it does have to be moved to every single Gateway. So, the Enterprise Administration Module, you just make that change one place and then push it out to all instances. Definitely check out the Enterprise Administration Module.
Kent: So then, once you've created that parent project, you start creating those parameterized views. In my case, I created a bunch of views in it. But specifically in this Embedded folder, I created a Device view, I created a Motor view, and I created a Title that I used throughout. These are the building blocks that I used to run my projects, so I'll show you guys that when I jump into the designer here in a minute. But parameterized views are the equivalent of what templates are in the Vision Module, if you're familiar with Vision. And so these views will be embedded, repeated, or placed on canvases, and overall just used to build out all of your final screens. I'll show you adding those to a bunch of projects as we get into the designer here.
Kent: Other types of resources to include in there, I mentioned some of these before, but could be styles, could be themes, named queries, scripts, reports, alarm pipelines, your navigation and page config. All of those could be inside your parent project and then just inherited throughout.
Kent: At that point, you'll go and you'll start creating projects that will inherit these. And it's worth noting, on a single Ignition Gateway, you can have as many projects as you want. And so a project could represent a site, like what I'm doing here. A project could be just an area within a site. It could be a specific set of screens for a type of user. Really it can be any logical grouping of screens that makes sense to separate out, And so if you've got just a dashboard project that's just for C-level people to view or managers to view, or maybe you've got a project that is just for your QA team to go in and look at some values, you can start adding as many projects as you want. But in my case, it's really clean to show, from the object-oriented standpoint, I've got a site, I'm gonna have a site per project, and I'm gonna show a bunch of projects, and so there's some consistency there. That's what we're doing today. But, yeah, definitely, you don't need to shy away from having multiple projects on a single Ignition Gateway.
Kent: All of that is leading up to this. Like I said, I'm an engineer, I wanna get into the demo and show you what I built, show you how it works, and to see how all these pieces fit together. And so I'm gonna go ahead and jump now to my Ignition Gateway here. This is an Ignition Gateway, You can see I was just tweaking around with this. This is just a trial mode. I just downloaded Ignition off of our website. I have just the base modules installed on this.
Kent: The first thing I did is I went to config here. I came down and I needed some device connections. In my case, I created five devices. And all of these devices are very similar, they're all using our programmable device simulator. If you're not familiar with the simulator, you get to load a program onto the simulator, there's a bunch of options to choose from. I'm going with this Dairy Simulator 'cause it'll give me some objects I can use. I just have all these devices, that was the first step. Same thing for you guys, when you're starting a project, you wanna connect to your devices, you can start building out that data model.
Kent: The other thing I did inside the Gateway is I don't have a database connected to this Gateway right now, and so I just went into tags history, and I created a new historical tag provider, and I just used our Internal Historian. It's just kind of a built-in database that you can store history to. Because I'm trying to keep it simple today, I'll just use that Internal Historian. And that's really all I did on this Gateway webpage. Nothing else in here.
Kent: You could certainly go in and do a lot more here, set up redundancy, set up your users and roles, and security, and all that stuff, set up an alarm journal. But today, we're just doing real-time tags in history. At this point, I jumped into the Designer, and that's really where the magic starts happening. You can see I created a project called Site Template, and I created Site 1. We're gonna start by talking about this site template and show you everything that's in here. And this Site 1, you can see inherits that Site Template. We'll show you how that works. If we didn't do any live development on the fly, this wouldn't be interesting for you guys, so we'll also show you... This is supposed to be dynamic, so we're gonna spin up another project at the end, that's Site 2, that's gonna inherit this template, and we're gonna show you how easy it is to get Site 2 up and running.
Kent: So, Site Template here, I'm gonna go ahead and open that guy. My Designer is launched now, and so the first thing I talked about doing is creating your data model. Inside Ignition, that's your tags, and so I came and I have these data types, and I've got a type called Motor. And the way I based my data model on was, I came in and I looked at those devices I connected to, remember those five devices. And I said, "All right, inside my device, I've got a bunch of motors here, and so I'm gonna create an object called Motor, a UDT, and inside Motor, I want to have Amps and HOA," 'cause all of these share amps and HOA. And then I created my own custom tag as well, a memory tag that would show the motor name.
Kent: But if I come into this guy, how this works, my UDT, I don't wanna point to a specific device. I want it to be generic. I want this to be reusable. And so I came in here, it's just called Motor, and it's got parameters. And remember parameters are what we're gonna pass to this so that it can be dynamic, so that each instance we create after this is gonna point to a different set of data. And so I said, "I'm gonna have two parameters. I'm gonna have a DeviceNum and a MotorNum." I can say this is coming from my first device, and I want it Device 1, and then I want it to be my first motor, or my second motor, or any of those eight motors I showed.
Kent: Where these parameters come into play is in my... in my type structure over here, I have tags. And inside these tags, I've got my OPC Item Path, that’s saying where in your PLC am I getting this Amps value from. And instead of hard-coding a value, what I did is I used my DeviceNum parameter and my MotorNum parameter inside my address here so that it would grab Device 1, in my case, if I pass in a parameter of 1 for my DeviceNum, and then grab Motor 2, for example, within that Device 1, if I pass in a two parameter to my motor. I've done this dynamic addressing for each of the tags here, Amps and HOA. You can see inside HOA I've got that same thing set up.
Kent: Now, I also went ahead and added a memory tag. This isn't coming from the PLC, it's just something that's in Ignition, a convenience tag for me. And what I did is I made it a string, and I gave it Motor, and then passed in MotorNum there as well, so it'll be Motor 1 or Motor 3, or whatever I pass in as the parameter for MotorNum. And so this was my motor structure.
Kent: Other things I did inside this structure, there's definition. I went ahead and enabled history, and started storing to that Internal Historian for my Amps tag. At this point, I don't actually have any tags, this is just a definition, it's just a structure, it's a reusable object, doesn't have any instances yet. But before I go in and start creating instances, I said, "All right, well, each device has a bunch of motors." And so if I come in here, I created another UDT called Device. And inside Device, I have a bunch of motors. I wanted to pass in my DeviceNum as a parameter to this guy, so it'll be device 1, 2, 3, 4, so on. And then inside, I have these child UDTs, I have all my individual motors and they have parameters as well. DeviceNum, I'm actually inheriting the parameter from my device. And then MotorNum is the separate parameter where I pass in 1, 2, 3, 4, whatever my MotorNum is.
Kent: Essentially, I've created this additional UDT that represents a device, and each device is gonna have several motors in it. That's my second UDT. And all of these motor instances, if I go and change my motor definition, it's gonna reflect that inside this device definition as well. You can see I have Amps, storing history on these guys, so it's inheriting all of those properties.
Kent: To get through this, I'm gonna go a little faster here. I've got my data types, my UDTs, user-defined types. Now I wanna create instances, and so I created an instance for Device 1. It has my Device 1 parameter. I created Device 2. I actually have five devices, so I'll go ahead and show you what it's like to create a device here. I can just go ahead, and in my tags folder here, I'm going to say new tag, create a data type instance of device, and this one is gonna be Device 3. And for Device 3, I have a parameter to pass to it. I'm just gonna pass three, and Commit, say OK, I now have Device 3 here and I can see its respective tags. Already we're seeing it's easy to add additional instances. If I went and bought another device, I'm gonna put it in, or in my case just connect it to another simulator. It's really easy to now add a new set of seven motors instantaneously, pretty much.
Kent: That's my data model. I've got a bunch of devices. It's also worth noting, inside Ignition, when I said I was in the Gateway webpage I didn't do anything else, I lied there. I forgot about one other thing. I made it... Inside Ignition, you have tag providers, real-time tag providers. These are essentially just root level folders for your tags. I have Site 1 and Site 2. Everything I'm showing you right now, I just have Site 1 as my default tag provider, but I could have a... I'm gonna show you separately that I'll have each project, Site 1 project, Site 2 project, will have its own tag provider. I'll show you that as we go. But essentially my data model starts. If I look at all providers here, I have Site 1, I just created those devices. I also created a Site 2 that has two more devices connected. And so you start to build out your structure, going to sites, to devices, to motors, in my case.
Kent: Now, the next thing I want to do is visualization. Inside, now I wanna go to Perspective. In Perspective, you have this concept of views, and I chose... There's several views in here, 'cause when I created this project I chose one of our project templates. I chose a web nav. It's got a menu in it. It's got a header piece with a breakpoint. I added a pop-up, and I added these pages. So, it's got a home page, and then I have a Devices page, but really what I wanna talk about is these Embedded pages.
Kent: Starting with the motor, I created a view for my motor UDT. And just like my motor UDT has parameters, where... Sorry. If I come in here for motor... I accidentally moved something there. Okay. Coming in to Motor, I had Parameters. I passed in DeviceNum and MotorNum, and that's how I can point to different motors within different devices. I'm gonna do the same thing for this parameterized view. I created a view called Motor, and over here in my properties on the right-hand side, I have a DeviceNum property and a MotorNum property. And so, whenever I create an instance of this view, I'm gonna pass it a DeviceNum and a MotorNum, and that's how it'll know what data to grab respectively.
Kent: What I did when I created this template is I added a label on top. I brought in from Symbol Factory one of our graphics here. I added an overlay on top of it. Its color is based on HOA. I added Amps. I have the actual HOA button, which allows me to do control. I also added a link to a pop-up here, so when I click on that it opens up history of that Amps tag and a little chart, so just created a template here essentially. A template is what it's called in Vision here, it's an embedded view is what we'll use this as, a parameterized embedded view.
Kent: I created this motor, and then I created a device, and my device is gonna have a bunch of motors. And so just like my device UDT inherits several motors, my device parameterized view inherits or contains many motor parameterized views. And just like I have a parameter in device that is going to be the DeviceNum, I have a parameter for my device parameterized view that is DeviceNum, and so I can just pass it to 1 here and I know I'm looking at Device 1.
Kent: How did I add all of these motors here? If I come and look at this guy, what I did is, there is a Flex Repeater here. A Flex Repeater is just a component that you can pull out on screen. And so if I type in "flex," I have this Flex Repeater. And this Flex Repeater, what it allows me to do... I accidentally pulled out another Flex Repeater. I don't want that. This Flex Repeater has instances, and these are all gonna be instances of the same parameterized view. That's one of the limitations of the repeater, is that it's repeating the same view over and over. But in my case, what am I repeating? I am repeating Page/Embedded/Motor. Over here, I can see Page, Embedded, Motor. That motor parameterized view I created, I'm just repeating that here in my repeater, my Flex Repeater. And for all these instances, I'm going to pass it whatever parameters I have on that view.
Kent: And so I have DeviceNum and MotorNum. In my case here, I am passing the same DeviceNum every time 'cause it's a device page, but I'm passing a separate motor every time. I could manually go and add all of these instances, but I wanted to make this a little more dynamic. So, what if I add an additional motor, and I want this to reflect the correct number of motors without having to manually come in and put it? And so that's where bindings come into play. What we've done so far, we created this motor, we created a device page, and it has all these motors in it to make this dynamic. Now, we want bindings. Bindings allow you to bind any property inside any component to a tag, or to a property of something else on screen, or to an expression, or to a query, or to tag history. In my case, I'm binding to DeviceNum because I want all my instances to be the motors included with my device. And so I need to know the DeviceNum, my parameter. But I don't want to actually just bind to DeviceNum because I'm not looking for a number here. I'm looking for a complex array that has all these instances in it.
Kent: And so when you have bindings, you can do something called a transform, and you choose a type of transform. In my case, I did a script transform. But there's also mapping transforms, which is how I did the colors on my motor graphics here. I could do formats, I could do expressions, but I did the script. And what this script is doing is it is going to grab my device and it's actually gonna do a system.tag.browse to find all of the motors inside that UDT. And it's gonna get the length of that, and that's gonna be my quantity of motors. And then I'm gonna loop through all of that quantity of motors, and I'm gonna add an instance of that every time where I use my DeviceNum that I bound to up here, and pass that in. And I'm gonna grab MotorNum from my loop here.
Kent: What that does, like I said, is it's gonna go, it's gonna look in my tag structure, it's gonna find Device 1, 'cause I passed in a parameter of 1 to this whole guy. And in Device 1, it's gonna loop through and it's gonna find everything that matches a motor. In my case, it's finding seven motors, and so it created seven motors here — 1, 2, 3, 4, 5, 6, 7 — and created all those instances for me.
Kent: Now, what if I added an eighth motor? I want this to be dynamic. I happen to have eight motors, so let's go ahead and change my number of motors inside my device, and we'll see what happens with this guy. I'll go into my device type here, and I'm gonna add another motor, and this is gonna be Motor 8. And the parameters that I'm passing to this guy, this is gonna be inherited from my device. I wanna grab DeviceNum. But this guy here, this one is gonna be my eighth motor. I commit that, say OK, and so now you can see I've added an eighth motor. And that was added into all of my instances, so I can see Device 1, 2 and 3 all have an eighth motor. And if I come back over here and I refresh this binding, say OK, you can now see that it added an eighth motor for me.
Kent: That's the beauty of this whole thing, is that I changed my data model, I add another motor, and now my device page can dynamically now show that eighth motor really easily. And I didn't have to come in and change properties or anything, I just changed the data model. It's automatically reflected. Pretty cool. And so, yeah, these bindings are pretty cool.
Kent: How did I figure out this structure to start dynamically creating new instances? All I did is I came over here and I just copied an instance. If you copy an instance and I paste it, you can see it's already got that nice JSON structure for me, and that's where I was able to come in and just say, "This is actually based on param's DeviceNum," say OK, and then this one is gonna be based on some other property. In my case, it was my MotorNum in my loop. You don't have to be intimidated by creating these dynamic arrays inside Ignition. We're gonna show you how to use it in one other spot for the navigation later, but you can just copy and paste the structure into your script transforms.
Kent: We've got this now, but this device here... I'm showing it all with Device 1, but I don't want this page just to be for Device 1 and I have to create a separate view for Device 2 and a separate view for Device 3. I want this to all be dynamic once again. That's just my device parameterized view. I am now creating another view called Devices. And Devices, all it includes in it is an embedded view. An embedded view is just another property, or another component I should say, that you can grab from the property editor here, embedded view, and I pulled on my embedded view and I am embedding. I come over here, and I select... Just like with my repeater, I selected a path. For my embedded view, I select a path. In my case, I'm grabbing Page/Embedded/Device, so that device page we were just working on, I embed that, and then I pass a parameter to that. And so I'm passing DeviceNum, and this could be Device 1, Device 2, Device 3, whatever.
Kent: Once again, where am I getting this? Well, I created a param on the devices page, which is DeviceNum here, too. What's cool about this, and this is kind of more of an advanced feature, I could certainly just create a separate page in here for Device 1, Device 2, and so on, but to show you really dynamic, I did not create a separate page for a device. Instead, I just created this one generic devices page that's parameterized. And where am I getting this parameter passed in from? Well, inside Ignition, you create navigation. I have this home page, which is mounted to my root URL, and then I created another page, which is gonna be a link to my Devices page that we were just looking at. And what's the URL for that? Well, it's gonna be devices, but then I'm gonna have the second part of the URL be : DeviceNum. What this means is I'm actually passing a parameter directly from the URL.
Kent: So, if I did Devices 1, then it would navigate me to the Devices page and it would pass DeviceNum a 1. And within a page URL, you can have as many of these parameters as you want, and those parameters will just get mapped to the parameters on the page itself when it navigates there. Pretty cool. And so that's allowing me to just have a Devices page and not have to have a page for each one. If I wanted to now see that in action, so I can show you guys what I mean, well, I can't do that from a parent project, 'cause a parent project is not launchable. Instead, you need to launch from an instance.
Kent: I've shown you what is built out here, I showed you a device page, a motor page, and now I've got this dynamic page config for devices, where I'm passing in a DeviceNum in the URL. And I should... Before I show you the launch, one other aspect of this is I have a menu. And my menu here is dynamic as well because it has a list of items, it's an array. And so same idea, I could come in and I can manually add each of these elements inside my array, and I could say that this is my home page, and this is the icon it's gonna have, and if there are a child items, and this is the target path I'm navigating to, it can manually set all these things. But I wanted to add a home page, but then I wanna have... However many devices I have, I wanna have those all listed here as items in my menu.
Kent: Once again, I did a binding, just like I did a binding on the instances in my Flex Repeater. And what I did is I copied this structure here and set it for home, and then I append that to my nav items here. And then for my devices, I once again look up... I'm doing a system.tag.browse, where I am looking up all my devices, and I'm looking specifically for UDT instances. I'm looking for Device 1, Device 2, Device 3, those kind of things. And so with that, when I get them, I grab the quantity of devices, and I loop through and I add an instance for each of my properties here. So, I'm grabbing Devices and then DeviceNum here.
Kent: Just another dynamic thing, I'm doing a dynamic menu, that's how I did that. Also, if any of you are wondering, I'm certainly gonna make this project available so that you guys can reverse-engineer this and see how it works, so you don't need to worry about taking copious amounts of notes of how I'm doing this on the fly. Yeah, this is dynamic. I have three devices and I have three items there. If I came in and removed a device... Let's just cut one for a second... It's not gonna let me do that. I'll delete one. I deleted a device, I come back, and I refresh this binding here, say OK. You can see now it just shows Device 1 and Device 2. If I come back here now and I create another instance, a new tag, data type instance, device, and this is gonna be Device 3 again. And I pass in 3, it's my parameter, Commit, say OK, I now have that Device 3 back. And if I come in to my binding and refresh that, you can see I have three devices again.
Kent: So, my menu is dynamic as well. But like I said, this is all in the parent project. Everything we've done is in a parent project, so it's not launchable yet. I'm gonna go ahead and say... I want to open... Sure, we'll save our changes. And I wanna open up this Site 1. When I created this Site 1, how it worked, I just went to New Project and I chose Site Template as my parent project. And like I said, I'll show you creating Site 2 here in a minute so that you can see how that works. It looks like it remembers I had this opened earlier and is opening the same views here. Here we go. All right.
Kent: What I'm gonna do, you can see I've added a couple of things to this, but I'm just gonna go ahead and launch this now. You could certainly go in and launch this from your Gateway webpage as well. But as this launches here, you can see I've got a home screen, I've got my dynamic number of devices here. And if I click on this, I can see all my motors there, I can interact with them, I can view a trend of these guys, all that kind of stuff. I can toggle between each of these devices and see all of their individual motors and, yeah, it's all dynamic. That's pretty cool. Once again, if I wanted to roll out this to another site, I have that data model. And so I came in here, I have my tags, but I said I've created those providers, so I already have a Site 2 where I've copied over my data types here of device and motor, and then I created Device 1 and Device 2 for that area 'cause remember I had a total of five devices.
Kent: Now, if I wanna create another project that's based on Site 2, what I can do here is go File, New, New Project, sure, we'll save our changes here. This project is gonna be called Site 2, and I'm gonna point it to my Site 2 tag provider. And then in my project template here... Well, I don't want a template, I want my parent project of Site Template, and go ahead and create that project. Now it's creating Site 2, this is me rolling out to a new facility, so to speak. One note that I didn't talk about before, I do reference a session property that's my site here in a couple of places, and so I'm gonna change that to Site 2. But with that now, I'm just gonna go ahead, save it.
Kent: And if I come back to my Gateway here and I go to my projects, I can now launch my Site 2 project I just created. This one has two devices, and I can go in and I can see those devices. You can see the simulator hasn't been touched for these guys 'cause we've been working on the other devices, so if I come in here and change some things around, I can see all those respective values here. Yeah, really easy to spin this up. It automatically grabbed the devices I had, it grabbed the motors that are inside those devices. You can see I made that change to add the eighth motor to my other site. I didn't do that here, so you can have some differences as needed. Yeah, that's a quick look at starting to do dynamic design.
Kent: Now, there's a lot more to this that you can start to do. I wanna hit just a couple of things. In addition to the repeaters, you have some other types of ways to view parameterized views to put them on to your pages. I'm gonna go ahead and open up Site 1, I added some custom stuff on Site 1, because, once again, I'm inheriting all of the stuff that I put in my parent project, but I can also add whatever custom stuff I want inside my project. What it looks like when you inherit, you can see everything that's great out here, like my styles, my folders here, anything that is inherited from my parent project is gonna be great out. And I could certainly say, "Well, I actually wanna modify my header," so I could right click and say Overwrite Resource, and then it would make a copy of that essentially, but it's one that is my local copy here to Site 1 that I can change and do whatever I want with.
Kent: In my case, I didn't modify anything. I just added two more pages here. I created a canvas, and this canvas is pretty cool. Unlike the Flex Repeater, a canvas allows you to put different parameterized view types inside the same canvas. I could only repeat motors over and over again in a repeater. In a canvas, I can put different types.
Kent: Now, I only created one type, created Motors, and so they're still all motors here. But also, instead of just repeating them, I can define where they live, I can define a width and a height and an X and a Y location inside a grid, and then I can actually have it dynamically move them around. I can add scripting on it to change their location and things. The parameters, for all that, if I come in to this guy here. I look in my canvas, once again, I just have instances. My instances could be dynamic based on the number of motors. Once again, in my case, I didn't do that. But then it's got a View path, a path that's seen at my motor. And it's gonna have my position properties, it's gonna have a top, a left, a Z index, a width and a height, all that kind of stuff.
Kent: But this one's pretty cool. If I actually navigate to this page that I created... I'll go ahead and launch this URL here. And this is just showing I'm using all those same motor, embedded, or parameterized views, and now I can toggle between them here. And so this is just using some scripting. When I click the arrows, it changes the Z order, it changes the... Not X and Y, in this case, it's top and left, but the position of it. Just another cool thing to play around with. You can have it be... When you click on something, it changes size, or it moves, or have arrows like this, you can have it be dynamic as you want. Certainly those can be pretty cool. Check out the Canvas, and like I said, it can have different types.
Kent: The other way you can display parameterized views that's pretty cool is dashboards, and so I created a dashboard as well. Dashboards, rather than you defining everything upfront and then just displaying it inside the client, it can be editable from the client itself. And the dashboard, once again, it's just another component that you can pull in and use. Here I have a dashboard, I already pulled one out. Essentially, you give it a grid, and so I have an 8 x 9 grid here, and then you define available widgets that somebody could add on. I added six widgets, they're all motors, in my case. They're Motor 1 through 6. But I could certainly have compressors, or other items in here if I had built out things in my data model and built parameterized views for those. And in my available widgets, the way I'm doing it here is I am passing it, my motor parameterized view, and then I give it my view params for that DeviceNum and MotorNum. And so you could also have available widgets be dynamic, where it generates this all based on however many motors exist for the device, or you could have a look through all the devices, all that kind of stuff.
Kent: To show this in action, once again, if I go to launch a project here, gonna go ahead and launch my dashboard. Then now inside the client, I'm in editing mode right now, and so somebody could come in here and say, "I want to add a Motor 2, I wanna add a Motor 3." If they wanted to, they could resize these as appropriate. And they could create their own dashboards that show whatever components they wanted. In my case, it's just Motors. Once again, it's kind of boring, but you could have users creating their own dashboards just showing the parameterized views that they care about, or in this case the widgets. And so if I hit play here now, then I can interact with these as if it's a pre-built screen. If you haven't played around with the dashboard component, it's really cool. I highly recommend you check it out. Especially once you've already got all these objects here, it's really easy to create all those widgets based on those.
Kent: Yeah, there is that. We are really needing to get to some questions here. The last thing I wanted to mention is you have styles. When I'm creating things, like if I come to my... Actually, let me open up my Site Template again here, Site Template. When I'm creating, say, my motor parameterized view, then I wanted to have background colors and things like that, and you can define the color scheme for your motors based on inline styles or by an object for style. In my case here, I've got up here Motor 1, is my title on top of this guy. And inline, I said I want that to be of a font size 18 and be bolded, but also I wanted to have that gray background. But rather than defining that, "everywhere else I want a gray background," I created what's called a class. And I added two classes to this essentially. I created... I added centered, 'cause I wanted a text-centered, and I have one called Title here.
Kent: But to show you what those are, over in my project browser, I have these styles. And under Title, I can create this rule for tile. And titles have a background color and they have a border, and so rather than having to inline add this everywhere I wanna use Title, I create this object of a style called Title, and then I can apply that as a class to any object I want, any property I want inside my project. This is another type of object, this is another building block you'll use as you'll create styles.
Kent: Now, it's worth noting that we also recently added support for themes. In themes, you can actually toggle it between a light and dark theme, or create your own custom themes. And so, rather than just setting my background color to be like a gray, I'm setting it to a variable inside my theme. And you can go into our online documentation, I don't have time to go into all of that today, or we have other community lives that go just specifically into themes, but I can put in this neutral 30 here. The effect of using these variables inside all your colors, in your styles and inline styles, is now if I come to my Devices page here and I toggle my theme here, I can switch this to a dark theme, and I can see my background colors all update. My text color has changed from black to white, 'cause here they're black, here they're white, and so on. The other part of making a dynamic design is styling it in a dynamic way so that you could switch out your themes, you can quickly roll out different styles to a bunch of different objects.
Kent: We tried to cover a lot here in an hour, I know we went pretty fast. Like I said, I'm gonna make this project available to you. I'll either put it... We'll ask Amanda if we should put it on the Exchange, or if there's a better place to put it, when she comes on here in a second, but... Yeah. We'll make it available to you. You can certainly reach out if you have questions to your sales representative, and they can arrange a meeting with the sales engineer. We're happy to talk about these kind of concepts with you. With that being said, let's go to questions. Amanda, having you jump in here, first question: “Where can they get access to this project? Shall I put on the Exchange or do we have a way to put it up with the video somewhere?”
Amanda: I think that you are able to add it to the Exchange for easier access in terms of adding the project along with a video recording, I don't think we have a method to update, or to add that to the video recording just yet, so maybe just the Exchange for now.
Kent: Perfect. So, I'll put this up on the Exchange. I'll label it with the name of this Community Live, so it's easy to find. I'll have that up there. We do have some questions here. The first question is, "Can you add a child tag to an instance of UDT if that instance is slightly different than the other ones, or do I have to create a different UDT for that?" That is a great question.
Kent: You can't just add... In my case, I added an additional motor, an entire new motor. UDT is a child to my definition of a device. And so I can't just add another tag or another instance to one instance of a device. I couldn't just add another motor to one instance. What people do to get around that is you can have multiple UDTs that inherit other UDT, so that you can have a dynamic number. Or you can just use a folder structure that... So I could have a folder for Devices, and then I've got instances of motors inside those folders. But also sometimes people will have additional tags in there, but they're disabled in some of them.
Kent: And so I'd say I could have up to eight motors, and so I'm going to create eight instances in my definition of a motor UDT. But I then dynamically check how many motors actually exist, and then I disable the ones that don't exist for that instance. So, no, it can't really just add or remove tags inside a UDT, but you certainly can still accomplish that by doing it in a folder structure or by just having the max number of tags and then disabling the ones you're not using.
Kent: Another question we got was, “Can we inherit a view? Let's say I have a view, Pump 1 and I need a Pump 2 view inherited from Pump 1 with some few attribute modifications.” Rather than just making a separate device, if I come in here, and so you've got motor in my case here, page... No, sorry, in the style still gotta come down the page. I've got a motor. In my case here, all my motors are this type. But say you've got a Motor 2 that's slightly different, and so you wanna overwrite things that are inside this. I could make a copy of... And my trial just expired, which is why that just all in red there. I could make a copy and then just tweak those, and so then I end up having multiple versions of an object type. But that object type can't necessarily just inherit the view here and then tweak it. Where you could do something like that is if I could define it inside my parent project, and then when I actually go to my site project here, then I could overwrite the definition here, but it's gonna overwrite it for every time I use it.
Kent: In this case, you probably just wanna make a copy of it. In my case here, coming in to embed it, I could right click on Motor and say Overwrite Resource, and then I could tweak this motor. It would start by inheriting the motor from the parent project, but then I could come in and change the size of something or whatever. Inside child projects, yes, you can inherit something and then modify things, but often you're just gonna make a copy and have multiple types of pumps in your case.
Kent: Let's see. We are out of time. I appreciate everybody who asked questions. We got to most of them. Thanks, everybody, for participating. Back to you, Amanda.
Amanda: Great. Thank you, Kent. And thanks for joining, everyone, and have a great rest of your day.