Styling a Custom Component in userModule
-
I have gotten the depiction of a machine in a dashboard to work as desired, but I am having trouble wrapping my head around how to make it component.
Here is what I am doing right now:
Some css to style the background of the image to indicate status:
.idle { background-color: rgba( 255, 255, 255, 0.5); } .heat { background-color: rgba( 255, 255, 0, 0.5 ); } .air { background-color: rgba( 3, 186, 252, 0.5 ); } .heatair { background-image: url("/rest/v2/file-stores/default/HeatAir.png"); } .a0 { background-image: url("/rest/v2/file-stores/default/A0.png"); } .s1 { background-image: url("/rest/v2/file-stores/default/Stage1.png"); } .s2 { background-image: url("/rest/v2/file-stores/default/Stage2.png"); } .s3 { background-image: url("/rest/v2/file-stores/default/Stage3.png"); } .manual { background-color: rgba( 0, 0, 0, 0.5); }
A call to ma-get-point-value to get the status of the machine:
<ma-get-point-value id="a5ed9ee7-925f-4950-a46a-e4b02113c062" style="position: absolute; left: 610px; top: 530px;" point="sequence1" point-xid="DP_6122694c-e93e-48d0-a423-2b23d88b45f9"></ma-get-point-value>
And ng-class to set the class based on the status of the machine:
<img id="ed3362f5-9f30-47f8-877b-6eb1fa0f3f19" style="position: absolute; left: 125px; top: 170px; width: 30px; height: 28px;" src="/rest/v2/file-stores/default/HKDStickL.png" ng-click="designer.parameters = {dn: 'sg-lsb-01v'}" ng-class="{0:'idle', 1:'heat', 2:'air', 3:'heatair', 4:'a0', 5:'s1', 6:'s2', 7:'s3', 8:'manual'} [sequence1.value]">
This works perfectly, except for one thing. Adding machines is more work than I expect my users to do acccurately. The obvious answer seems to be a custom component.
I have built a userModule and started to write the code to implement it, but I am having trouble figuring out how to style the component based on a data point.
-
@pyeager
Take the datapoint value as a binding input. If you're using a component, use theif(e.ptVal && e.ptVal.currentValue!=undefined) runValCheck(); }
After you check your value, (in this case
this.ptVal
) use the angular.addClass and removeClass functioms to alter your component's styling. With this in mind have a second variable to hold the last class value so you know what to remove.
I'm a few hours a away from work. Can assist further when at the PC.Fox we
-
@mattfox I'm brand new to angular. Where does the code you provided go?
-
Ah right sorry,
From what it looks like, you want a generic solution to provide users a drag and drop component I'll give you the bones of a component to fill in. The html should probably be in a separate template file.
Is your userModule in the mango file store or on disk?Fox
EDIT: Have you made a start? If so we can build from that.
-
@mattfox said in Styling a Custom Component in userModule:
From what it looks like, you want a generic solution to provide users a drag and drop component I'll give you the bones of a component to fill in. The html should probably be in a separate template file.
Yes, I need to build a drag and drop component. I have already figured out how to use a separate template file. Beyond that, the code is little changed from the example code.
define(['angular', 'require'], function(angular, require) { 'use strict'; var userModule = angular.module('userModule', ['maUiApp']); userModule.component('stickGun', { bindings: { name: '@?', face: '@?' }, templateUrl: '/rest/v2/file-stores/public/userModule.html', styleUrls: ['/rest/v2/file-stores/public/userModule.css'] }); return userModule; }); // define
Is your userModule in the mango file store or on disk?
It is in the public folder in the file store. That also appears to be on disk at /opt/mango/filestore/public
-
Cool thanks Pyeager, I'll get something knocked together now. Secondly, you're best to use a separate template url per component. I'd strongly suggest creating a /rest/v2/file-stores/public/stickGun.html html file, so you know what the contents are without having to open it up.
That also appears to be on disk at /opt/mango/filestore/public
Sorry I meant outside of mango in the overrides or something. Just in case upgrades or something causes an errant spanner to be thrown... ;) - It also allows you to add source control so you can store your files in a git or svn based solution, very handy for versioning, and who to blame when something breaks (haha)
Fox
-
Here's something to get you started, I'm not sure what the purpose of the ng-click is for your image, if you are able to explain i'll likely be able to assist with that part.
I am not sure what name and face are for either.
Basically with the component, you're bringing all of the dynamic variables to the surface as bindings for the component. From here you do all of your calcs and fancy doodads inside the component's controller.Your html:
<section> <style> .idle { background-color: rgba( 255, 255, 255, 0.5); } .heat { background-color: rgba( 255, 255, 0, 0.5 ); } .air { background-color: rgba( 3, 186, 252, 0.5 ); } .heatair { background-image: url("/rest/v2/file-stores/default/HeatAir.png"); } .a0 { background-image: url("/rest/v2/file-stores/default/A0.png"); } .s1 { background-image: url("/rest/v2/file-stores/default/Stage1.png"); } .s2 { background-image: url("/rest/v2/file-stores/default/Stage2.png"); } .s3 { background-image: url("/rest/v2/file-stores/default/Stage3.png"); } .manual { background-color: rgba( 0, 0, 0, 0.5); } </style> <ma-get-point-value point="sequence1" point-xid="{{$ctrl.pointXid}}"></ma-get-point-value> <!-- ng-click="designer.parameters = {dn: 'sg-lsb-01v'}" What is this for?? --> <img id="{{$ctrl.pointXid}}_img" style="width: 30px; height: 28px;" src="/rest/v2/file-stores/default/HKDStickL.png" ng-click="designer.parameters = {dn: 'sg-lsb-01v'}" ng-class="{0:'idle', 1:'heat', 2:'air', 3:'heatair', 4:'a0', 5:'s1', 6:'s2', 7:'s3', 8:'manual'} [sequence1.value]"> </section>
Your component
userModule.component('stickGun', { bindings: { name: '>?', //1-way binding optional face: '>?', pointXid:'>' //required }, templateUrl: '/rest/v2/file-stores/public/stickGun.html', styleUrls: ['/rest/v2/file-stores/public/userModule.css'], //not hugely needed, especically if you use the user styles file to store all your css in. Unless you plan to take this component to other places... controller:function(){ var ctrl = this; this.$onChanges = function(e) { //binding checks are done in here. this is for an example of what I stated prior if you are using optional bindings if(e.pointXid && e.pointXid.currentValue!=undefined) { ctrl.pointXid = e.pointXid.currentValue; } }; } });
-
@mattfox Thanks!
It's quitting time. so I'll play with it tomorrow.
When the image is clicked, ng-click set which device's details are shown on a card.
-
@pyeager This is what I would do, based off your code. Note that
styleUrls
seems to be Angular 2+ syntax, not AngularJS (1.x). Put your CSS in the user styles style sheet (under UI settings).User module -
define(['angular', 'require'], function(angular, require) { 'use strict'; var userModule = angular.module('userModule', ['maUiApp']); // doesn't do anything, but here for completeness class StickGunController { } userModule.component('stickGun', { bindings: { name: '@?', // dont know what these are for, but sure face: '@?', point: '<?' }, templateUrl: '/rest/v2/file-stores/public/stickGun.html', controller: StickGunController }); return userModule; }); // define
Template file (/rest/v2/file-stores/public/stickGun.html) -
<ma-get-point-value point="$ctrl.point"></ma-get-point-value> <img src="/rest/v2/file-stores/default/HKDStickL.png" ng-class="{0:'idle', 1:'heat', 2:'air', 3:'heatair', 4:'a0', 5:'s1', 6:'s2', 7:'s3', 8:'manual'}[$ctrl.point.value]">
CSS file -
stick-gun { display: block; } stick-gun > img { width: 100%; height: 100%; } stick-gun > img.idle { background-color: rgba( 255, 255, 255, 0.5); } stick-gun > img.heat { background-color: rgba( 255, 255, 0, 0.5 ); } stick-gun > img.air { background-color: rgba( 3, 186, 252, 0.5 ); } stick-gun > img.heatair { background-image: url("/rest/v2/file-stores/default/HeatAir.png"); } stick-gun > img.a0 { background-image: url("/rest/v2/file-stores/default/A0.png"); } stick-gun > img.s1 { background-image: url("/rest/v2/file-stores/default/Stage1.png"); } stick-gun > img.s2 { background-image: url("/rest/v2/file-stores/default/Stage2.png"); } stick-gun > img.s3 { background-image: url("/rest/v2/file-stores/default/Stage3.png"); } stick-gun > img.manual { background-color: rgba( 0, 0, 0, 0.5); }
-
Oh, and to use, you just add this to your page -
<stick-gun point="myPoint"></stick-gun>
-
Thanks Jared, appreciate the support!
-
Thank you both for your help.
I might need just a little more.
Specifying a data point by xid, as both of your examples do, is a bit less than optimal for a couple of reasons.
For one thing, there are multiple data points with which the component needs to interact.
Also, specifying one or more data points by xid is less than user friendly. My preferred approach is to just name the component instance in such a way that queries can be constructed from the instance name, removing the need to specify an xid for each data point.
...and additionally...
This may be asking a lot, but ideally adding this component to a dashboard would also create a few data sources, likely from a template. That would make the drag and drop addition of the component and definition of element-specific details pretty much all that is necessary to add a machine to the system.
-
@pyeager said in Styling a Custom Component in userModule:
Specifying a data point by xid, as both of your examples do, is a bit less than optimal for a couple of reasons.
My example does not specify a data point by XID, it passes a single data point through.
@pyeager said in Styling a Custom Component in userModule:
For one thing, there are multiple data points with which the component needs to interact.
In this case you could pass a whole array of data points through (from a query or Watchlist) and filter the points out inside the component.
@pyeager said in Styling a Custom Component in userModule:
Also, specifying one or more data points by xid is less than user friendly. My preferred approach is to just name the component instance in such a way that queries can be constructed from the instance name, removing the need to specify an xid for each data point.
I agree you should not directly bind components to a specify XID. This is why our dashboard designer supports selecting a watchlist then binding components to data points using their name.
If you want to use a name attribute for your components this would work in a similar fashion. Like I said above, pass the whole points array through to the component and then find the point you want inside the component using the name attribute.
e.g. controller for your component
class StickGunController { $onChanges(changes) { if (changes.points && Array.isArray(this.points)) { this.point = this.points.find(pt => pt.name === this.name); } } }
@pyeager said in Styling a Custom Component in userModule:
This may be asking a lot, but ideally adding this component to a dashboard would also create a few data sources, likely from a template. That would make the drag and drop addition of the component and definition of element-specific details pretty much all that is necessary to add a machine to the system.
Certainly possible, I don't have time to get right into this right now but you could use the
maDataSource
service to check for the existence of the DS and create it if one matching the name attribute doesn't exist. -
@jared-wiltshire said in Styling a Custom Component in userModule:
Like I said above, pass the whole points array through to the component and then find the point you want inside the component using the name attribute.
How does one pass the whole points array to the component?
-
By defining "points" as an input of the component, as I did in this post: https://forum.infiniteautomation.com/topic/3503/how-to-chart-with-data-points-in-the-x-axis-instead-of-time/6
-
@pyeager said in Styling a Custom Component in userModule:
How does one pass the whole points array to the component?
Are you asking how to define the binding in the component or how to pass the points to the attribute when you are using it?
To define the binding, you literally just have to change
point
topoints
in the example I posted above. I gave you a controller that will locate the point from the array using the name attribute.If you meant the latter, I suggest you peruse the examples that are built into Mango (enable the examples menu item using the menu editor if you have not already).
-
@pyeager said in Styling a Custom Component in userModule:
For one thing, there are multiple data points with which the component needs to interact.
You never stated this initially
@jared-wiltshire said in Styling a Custom Component in userModule:
Certainly possible, I don't have time to get right into this right now but you could use the maDataSource service to check for the existence of the DS and create it if one matching the name attribute doesn't exist.
If this is remotely similar to making a new point using the maPoint service perhaps I can be of assistance here