iOS DatePicker binding gives MvxBind:Error for viewmodel => view direction - mvvmcross

I am using an iOS UIDatepicker (weekendOnTimepick) in 'time only'mode in one of my views that needs to update the properties in my Viewmodel whenever the Picker view is changed. The direction Viewmodel => view is only important at View load hence is treated outside of the binding (see code below).
Although this works great (after overcoming the challenges of iOS wrt time localisation), I get a message "MvxBind:Error: 86.61 Problem seen during binding execution for binding Time for TimerOnWeekend - problem ArgumentException: Object type System.DateTime cannot be converted to target type: MonoTouch.Foundation.NSDate"
I guess this has to do with the binding direction I'm not using and doesn't seem to be an issue, but I do fear it will bring some sort of instability to my app.
Question : is this the right way of achieving the goal stated above, or would it be better to for instance ditch binding alltogether and use a View to Viewmodel subscription message with every Datepicker update ?
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
_userSettings = Mvx.Resolve<IHpRepository>().HpFullData.UserSettings;
var set = this.CreateBindingSet<TimerView, TimerViewModel>();
// TODO: check timepicker binding
set.Bind(weekendOnTimepick).For("Time").To(vm => vm.TimerOnWeekend);
int weekendOnHr = _userSettings.TimerOnWeekend / 3600 ;
int weekendOnMin = (_userSettings.TimerOnWeekend % 3600) / 60;
var weekendOnTime = new DateTime (2014, 1, 1, weekendOnHr, weekendOnMin, 0);
var AdjweekendOnTime = weekendOnTime.ToUniversalTime ();
weekendOnTimepick.SetDate (TouchConverter.DateTimeToNSDate(AdjweekendOnTime),true);
set.Apply();
}

Related

ngOnChanges only works when it's not the same value

So basically I have a modal component with an input field that tells it which modal should be opened (coz I didn't want to make a component for each modal):
#Input() type!:string
ngOnChanges(changes: SimpleChanges): void {
this.type = changes["type"].currentValue;
this.openModal();
}
that field is binded to one in the app component:
modalType = "auth";
HTML:
<app-modal [type] = modalType></app-modal>
In the beginning it's got the type "auth" (to login or register), but when I click on an icon I want to open a different modal, I do it like so:
<h1 id="options-route"
(click) ="modalType = 'settings'"
>⚙</h1>
but this only works the first time, when modalType already has the value "settings" the event doesn't trigger even though the value has technically changed
I think the problem is that it's the same value because i tried putting a button that does the exact same thing but with the value "auth" again and with that it was clear that the settings button only worked when tha last modal opened was auth and viceversa
any ideas? I want to be able to open the settings modal more than once consecutively possibly keeping onChange because ngDoCheck gets called a whole lot of times and it slows down the app
You need to include the changeDetectorRef, in order to continue in this way.
More about it https://angular.io/api/core/ChangeDetectorRef
Although, a better and a faster alternative is the use of a behavior Subject.
All you have to do is create a service that makes use of a behavior subject to cycle through each and every value exposed and then retrieve that value in as many components as you want. To do that just check for data changes in the ngOnInit of target component.
You may modify this for implementation,
private headerData = new BehaviorSubject(new HeaderData());
headerDataCurrent = this.headerData.asObservable();
changeHeaderData(headerDataNext : HeaderData) {
this.headerData.next(headerDataNext)
console.log("subscription - changeUserData - "+headerDataNext);
}
Explanation:
HeaderData is a class that includes the various values that can be shared with respective data types.
changeHeaderData({obj: value}), is used to update the subject with multiple values.
headerDataCurrent, an observable has to be subscribed to in the target component and data can be retrieved easily.
I mean i'm too l-a-z-y to use your slightly-not-so-much-tbh complicated answers so I just did this:
I added a counter that tops to 9 then gets resetted to 0 and I add it to the value
screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
//gets called onClick
openSettings(){
if(this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding === 9){
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
}
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding + 1;
this.modalType = "settings"+this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding;
}
then in the child component I just cut that last character out:
ngOnChanges(changes: SimpleChanges): void {
let change = changes["type"].currentValue as string;
change = change.substring(0, change.length - 1);
this.type = change;
this.openModal();
}
works like a charm 😂

Uncaught TypeError: Cannot read property 'setThemingColor' of null

I'm writing an extension for the Forge Viewer and I ran into this problem when trying to use the setThemingColor() method in the "load" part of the extension:
function extensaoteste(viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
}
extensaoteste.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
extensaoteste.prototype.constructor = extensaoteste;
extensaoteste.prototype.load = function() {
this.onSelectionBinded = this.onSelectionEvent.bind(this);
this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onSelectionBinded);
this.viewer.setThemingColor(3554,new THREE.Vector4(255/255, 255/255, 102/255, 1));
The code goes on, but the rest works fine. As you can see, there is another part of the extension, with an event listener.
If I use the exact same line with the setThemingColor method in the extensaoteste.prototype.onSelectionEvent, it works perfectly. I understand it is the this.viewer part that isn't returning anything, however it works in the line above.
I have used the code from https://forge.autodesk.com/en/docs/viewer/v6/tutorials/events/#step-2-listen-and-react-to-an-event as a template.
I know this is probably a silly question, but I really can't understand it. Thanks for your help!
This is happening because the model geometry was not fully loaded when the call was made to set color and the null reference was not called out on this.viewer but on the model object:
Viewer3D.prototype.setThemingColor = function(dbId, color, model, recursive) {
// use default RenderModel by default
model = model || this.model;
model.setThemingColor(dbId, color, recursive); // null reference here
// we changed the scene to apply theming => trigger re-render
this.impl.invalidate(true);
};
Try set color (and other fragment level operations) after the GEOMETRY_LOADED_EVENT is fired:
var viewer = this.viewer;
viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT,()=>viewer.setThemingColor(3554,new THREE.Vector4(255/255, 255/255, 102/255, 1));

Set instanceTree to a custom node in Forge 3D viewer

Lets say I'm working with a 3D file which is the combination of one Architectural model and one Structural model.
The instance tree or Model Browser looks like this
root/
Arch/
Level 01/
Level 02/
...
Str/
Level 01/
Level 02/
...
I want to display only the Level 01.
So I:
Followed the steps in the Viewer tutorial
Add an event listener to both Autodesk.Viewing.GEOMETRY_LOADED_EVENT & Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT
When the 2 are fired, I use the code in this article to display only the Level 01 without ghosting.
I have 2 problem with this approach
I have to wait until the entire model is loaded before I can filter the level
After filtering the level, if I click on Model Browser, I can still see the entire model structure (but with everything as hidden except Level 01). How can I set the instance tree to only have what's below?
root/
Arch/
Level 01/
Str/
Level 01/
EDIT
At what point am I supposed to override the shouldInclude() function?
I've tried this and put a breakpoint but it seems it never gets called... I also tried to move it around but in vain.
const start = Date.now();
Autodesk.Viewing.UI.ModelStructurePanel.shouldInclude = (node) => {
Logger.log(node);
return true;
};
Autodesk.Viewing.Initializer(options, () => {
Logger.log(`Viewer initialized in ${Date.now() - start}ms`);
const config = {};
// prettier-ignore
Autodesk.Viewing.theExtensionManager.registerExtension('MyAwesomeExtension', MyAwesomeExtension);
viewerApp = new Autodesk.Viewing.ViewingApplication('MyViewerDiv');
viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, config);
loadDocumentStart = Date.now();
// prettier-ignore
viewerApp.loadDocument(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
Regarding #1: the object tree is stored in the file's internal database which - for performance reasons - is only loaded after the actual geometry.
Regarding #2: you can subclass the ModelStructurePanel class and add your own behavior, for example, by overriding the ModelStructurePanel#shouldInclude method.
Since I wasn't able to understand how to use ModelStructurePanel, I overrode Autodesk.Viewing.ViewingApplication.selectItem to only modify the options which are either passed to loadDocumentNode or startWithDocumentNode as below:
const options = {
ids: leafIDs.length > 0 ? leafIDs : null, // changed this line
acmSessionId: this.myDocument.acmSessionId,
loadOptions,
useConsolidation: this.options.useConsolidation,
consolidationMemoryLimit: this.options.consolidationMemoryLimit || 100 * 1024 * 1024, // 100 MB
};
With leafIDs being an array of objectIDs to display. I was able to build it by:
querying the ModelDerivativeAPI using GET :urn/metadata/:guid
going through the tree to find the ids which I am interested in.
There's probably a more elegant way to do this but that's the best I could do so far.

How to handle $ctrl. in AngularJS?

I have a Methode from an API. It returns a promise which resolves to an $ctrl(?) object. This objects should contain a measurement and will be updated whenever it receive a new data.
getMeasurements.latest(filter) //only a object to filter through all measurements
.then(function (latestMeasurement) {
$ctrl.latestMeasurement = latestMeasurement;
});
My problem is that I don't know how to work with this data or display it in my html file. How does $ctrl work?
Here the documentation of the API
$ctrl is the view model object in your controller. This $ctrl is a name you choose (vm is another most common name), if you check your code you can see the definition as $ctrl = this;, so basically its the this keyword of the controller function.
So now if you are using $ctrl.latestMeasurement = 'someValue', then its like you are adding a property latestMeasurement to controller function.
Now how to use it in HTML?
To access the latestMeasurement property in HTML your code must have <h1>{{$ctrl.latestMeasurement}}</h1> (H1 tag is just an example.)
Here $ctrl is different from what I explained above on controller part. Here $ctrl is the value used for controllerAs property of the controller. But $ctrl is the default value of the controllerAs property, so your code may not have the controllerAs property defined, so Angular will take default value $ctrl in HTML.
This is where most people gets confused. So let me explain,
Assume in your new controller you have declared your this keyword to variable vm, and you set your controllerAs property to myCtrl, i.e;
controllerAs: 'myCtrl' while defining controller properties.
var vm = this; in your controller function.
In this case in js you have to use vm for setting values, and in HTML you have to use myCtrl. For example,
in JS controller function vm.test = 'Hello world';
in HTML <span ng-bind="myCtrl.test"></span>
The result Hello world will be displayed in your page.
Why $ctrl and not $scope?
The view model object model concept is introduced in AngularJS 1.5, it is actually part of migrating to Angular 2 where $scope no longer exsist. So in 1.5 they introduced new approch but did not removed $scope completely.
Hope the answer helped.
For basic Javascript concepts you can see http://javascriptissexy.com/16-javascript-concepts-you-must-know-well/
For more detailed AngularJS $ctrl concept you can see https://johnpapa.net/angularjss-controller-as-and-the-vm-variable/
I suppose you are toking about this.
In this case, the
$ctrl.latestMeasurement
can means:
$ctrl, the controller where you are running this code. You can change it by $scope for example, and get the same result.
latestMeasurement, the variable where you want to store the last value of the measurement.
To explain my point of view let see the code below
<div ng-app="MeasurementApp">
<div ng-controller="MeasurementController">
<h1>{{latestMeasurement2}}</h1>
</div>
</div>
There you can see a simple angularjs app that shows a variable called latestMeasurement2 in a div and its controller called MeasurementController. Then, to display the value let check your code.
angular.module('MeasurementApp', [])
// creating the controller
.controller('MeasurementController', function(c8yMeasurements, $scope) {
// creating the variable and let it empty by now.
$scope.latestMeasurement2 = "";
// Your code
var filter = {
device: 10300,
fragment: 'c8y_Temperature',
series: 'T'
};
var realtime = true;
c8yMeasurements.latest(filter, realtime)
.then(function (latestMeasurement) {
// The latestMeasurement is where the measurement comes
// Here we just assign it into our $scope.latestMeasurement2
$scope.latestMeasurement2 = latestMeasurement;
});
});
As the documentation says
// $scope.latestMeasurement2 will be updated as soon as a new measurement is received.
$scope.latestMeasurement2 = latestMeasurement;
Hope this helps!

Labels for tooltips on primefaces barchartseries

I have tried to search both the forum and Google extensively, but I have problems understanding how I should make this work:
PrimeFaces6
I have a BarchartModel based on the tutorial in the ShowCase:
CODE: SELECT ALL
private BarChartModel initStatusBarChart() {
BarChartModel model = new BarChartModel();
ChartSeries statusMessages = new ChartSeries();
statusMessages.setLabel("Label"));
statusMessages.set("Some String 1", list1.size());
statusMessages.set("Some String 2", list2.size());
model.addSeries(statusMessages);
return model;
}
The issue is that on render, I get tooltips the format of
"1, 515" and "2, 432", where 515 and 432 are the sizes of list1 and list2, respectively.
How can I replace 1 and 2 with the values "Some String" 1 and 2 ? Have tried extending highlighter and using dataTipFormat, with no success.
I solved the problem using the datatip editor of the chart model (with Primefaces 6.1, by the way). I used this for a stacked bar chart.
I needed to apply this solution at two places: the backing bean and the JSF page.
In the backing bean I had to set a JavaScript function name this way:
barModel.setDatatipEditor("chartDatatipEditor");
I tried to set it using the corresponding tag attribute in the JSF page but to no effect.
In the JSF I inserted this JavaScript code:
<script type="text/javascript">
function chartDatatipEditor(str, seriesIndex, pointIndex, plot) {
//console.log('chartDatatipEditor: '+str+" - "+seriesIndex+" - "+pointIndex);
var point = seriesIndex+','+pointIndex;
#{bean.datatipsJs}
}
</script>
This JS function gets the chart coordinates as parameters. I concat them so that the following JS code gets easier.
seriesIndex is the index of the chart series. pointIndex is the index on the X scale of the diagram.
To find out what are the correct values for your chart you can uncomment the console.log line above.
The inserted JS code is constructed in the backing bean this way:
private Map<String, String> chartDatatips;
public String getDatatipsJs() {
StringBuilder sb = new StringBuilder("switch ( point ) {\n");
for (String point : chartDatatips.keySet()) {
sb.append("case '").append(point).append("': return '").append(chartDatatips.get(point)).append("'; break;\n");
}
sb.append("default: return 'Unknown point'; break; }");
return sb.toString();
}
The map chartDatatips has the coordinate point as key (e.g., "2,1") and the tooltip as value.
During the chart setup you obviously have to fill this map with useful details ;-)
Like this:
chartDatatips.put("2,5", "Label ...");
...
Hope this helps, if you didn't already solved this.
~Alex
Based on Alex's answer I have come up with this. Only requiring javascript - it displays the label and value:
In the backing bean, set a JavaScript function name this way:
barModel.setDatatipEditor("chartDatatipEditor");
In the HTML file:
function chartDatatipEditor(str, seriesIndex, pointIndex, plot) {
return plot.series[seriesIndex].label + ' - ' + plot.data[seriesIndex][pointIndex];
}