I have an InterstitialAd QML object (QtQuick 2, QT 5.13) with onClosed event that is triggered with the interstitial ad is closed. I tried to show the interstitial ad before starting new game with the following QML code:
InterstitialAd {
id: iAd
property variant handlerFunc
onClosed: {
if (handlerFunc) {
handlerFunc
handlerFunc = null
}
}
}
function resetGameWithAd()
{
iAd.handlerFunc = Qt.binding(function() {
console.log("AdTest: calling resetGame()")
scene.resetGame()
})
console.log("AdTest: calling iAd.show()")
iAd.show()
}
where I tried to assign handlerFunc to a function that restarts the game when onClosed event is triggered, but got an effect that I did not expect. The console output of my app is:
qml: AdTest: calling resetGame()
qml: AdTest: calling iAd.show()
so obviously assigning handlerFunc to Qt.binding... actually calls the function (because resetGame is printed first), but I expected that it does only assignment. The similar technique is demonstrated here, with ':' but not with assignment.
What is wrong and what is the right way to implement this?
I also tried a code like this:
function resetGameHandler(params)
{
iAd.closed.connect(function() {
scene.resetGame(params)
iAd.closed.disconnect(/*...???...*/)
})
iAd.show();
}
but with no success because I can't disconnect it, without having a reference to the implicitly created function (as far as I see it means that I need a regular function with a name).
I haven't done any QML for some months, so I might be wrong. But if my memory is any good, this might help you.
To stay close to your approach:
variant is deprecated. Using var instead is recommended.
You won't need Qt.binding(). You can directly assign a function to that property.
Call the function in the property.
InterstitialAd {
id: iAd
property var handlerFunc <-- Use var instead of variant
onClosed: {
if (handlerFunc && typeof handlerFunc === "function") {
handlerFunc() <-- Call it!
handlerFunc = null
}
}
}
iAd.handlerFunc = function() { // doSomething cool here }
Alternatively you might be able to produce the same results with Binding and Connection-Objects in a more declarative way, but the right choice depends on what shall be done onClosed
Related
I'm using ngrx/component-store and loving it so far. Having prior store knowledge building my own simple ones, the only real headache I've had so far is when I've had to update an array and figured out I have to always create a new one for the internal compare() pipe to realize the array got updated.
Anyway, reading through the documentation it talks about updater methods and patchState. To me they do exactly the same thing, but their creation is slightly different. You would call patchState inside of a method while this.updater() returns a method giving you a function you can expose in your service. Anytime I'm updating my state it's always after a network call. I assume there are plenty of scenarios where you'd want to update your state without a network call so this is why you would want to have an updater available to your component to call. The question is if an updater and patchState are really doing the same thing then is it a better practice to call an updater in an effect or use patchState, or maybe am I putting too much logic in my effect?
On a side note, the docs say an updater method is supposed to be a pure function. If you're using it to your push an object onto an array then is it really pure?
// adding the selectors so people know what components are subscribing to
readonly approvals$ = this.select(state => state.requestApprovals);
readonly registration$ = this.select(state => state);
readonly updateAssessment = this.effect(($judgement: Observable<{id: string, isApproved: boolean}>) => {
return $judgement.pipe(
switchMap((evaluation) => {
const state = this.get();
return this.requestApproval.patch(state.id, state.companyName, evaluation.id, evaluation.isApproved).pipe(
tapResponse(
(result) => {
// is it better to call patchState()?
this.patchState((state) => {
for(let i = 0; i < state.requestApprovals.length; i++) {
if(state.requestApprovals[i].id == result.id) {
state.requestApprovals[i].isApproved = result.isApproved;
}
}
// the take away is you must assign a whole new array object when you update it.
state.requestApprovals = Object.assign([], state.requestApprovals);
return state;
});
// or this updater?
// this.applyDecisionPatch(evaluation);
},
// oh look! another updater reassigning my array to the state so
// it propagates to subscribers to reset the UI
() => { this.reverseDecision(); }
)
);
})
);
});
// this is private to make sure this can only be called after a network request
private readonly applyDecisionPatch = this.updater((state, value: {id: string, isApproved: boolean}) => {
for(let i = 0; i < state.requestApprovals.length; i++) {
if(state.requestApprovals[i].id == value.id) {
state.requestApprovals[i].isApproved = value.isApproved;
}
}
state.requestApprovals = Object.assign([], state.requestApprovals);
return state;
});
Note: There's no tag for ngrx-component-store so couldn't tag it.
An updater can be compared to a reducer.
All the options to modify the state should change it in an immutable way.
A library like ngrx-immer can be used to make this easier.
The main difference is that updater receives the current state, and you can change the state based on it. E.g. a conditional update, or can be used with #ngrx/entity
While with setState and patchState, you just set state properties.
setState updates the whole state object, whereas patchState only sets the given properties and doesn't touch the rest of the state object.
These two methods are also easier to use when you just want to set the state, because you don't have to create an updater function.
To answer the side question, push is not immutable. Instead of creating a new instance, it updates the array instance.
I have a Meteor Helper that does a GET request and am supposed to get response back and pass it back to the Template, but its now showing up the front end. When I log it to console, it shows the value corerctly, for the life of mine I can't get this to output to the actual template.
Here is my helper:
UI.registerHelper('getDistance', function(formatted_address) {
HTTP.call( 'GET', 'https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=Washington,DC&destinations='+formatted_address+'&key=MYKEY', {}, function( error, response ) {
if ( error ) {
console.log( error );
} else {
var distanceMiles = response.data.rows[0].elements[0].distance.text;
console.log(response.data.rows[0].elements[0].distance.text);
return distanceMiles;
}
});
});
In my template I pass have the following:
{{getDistance formatted_address}}
Again, this works fine and shows exactly what I need in the console, but not in the template.
Any ideas what I'm doing wrong?
I posted an article on TMC recently that you may find useful for such a pattern. In that article the problem involves executing an expensive function for each item in a list. As others have pointed out, doing asynchronous calls in a helper is not good practice.
In your case, make a local collection called Distances. If you wish, you can use your document _id to align it with your collection.
const Distances = new Mongo.collection(); // only declare this on the client
Then setup a function that either lazily computes the distance or returns it immediately if it's already been computed:
function lazyDistance(formatted_address){
let doc = Distances.findOne({ formatted_address: formatted_address });
if ( doc ){
return doc.distanceMiles;
} else {
let url = 'https://maps.googleapis.com/maps/api/distancematrix/json';
url += '?units=imperial&origins=Washington,DC&key=MYKEY&destinations=';
url += formatted_address;
HTTP.call('GET',url,{},(error,response )=>{
if ( error ) {
console.log( error );
} else {
Distances.insert({
formatted_address: formatted_address,
distanceMiles: response.data.rows[0].elements[0].distance.text
});
}
});
}
});
Now you can have a helper that just returns a cached value from that local collection:
UI.registerHelper('getDistance',formatted_address=>{
return lazyDistance(formatted_address);
});
You could also do this based on an _id instead of an address string of course. There's a tacit assumption above that formatted_address is unique.
It's Meteor's reactivity that really makes this work. The first time the helper is called the distance will be null but as it gets computed asynchronously the helper will automagically update the value.
best practice is not to do an async call in a helper. think of the #each and the helper as a way for the view to simply show the results of a prior calculation, not to get started on doing the calculation. remember that a helper might be called multiple times for a single item.
instead, in the onCreated() of your template, start the work of getting the data you need and doing your calculations. store those results in a reactive var, or reactive array. then your helper should do nothing more than look up the previously calculated results. further, should that helper be called more times than you expect, you don't have to worry about all those additional async calls being made.
The result does not show up because HTTP.call is an async function.
Use a reactiveVar in your case.
Depending on how is the formated_address param updated you can trigger the getDistance with a tracker autorun.
Regs
Yann
I'm trying to perform a custom sort using a comparator function from within a template helper in Meteor.
Here is my template helper:
Template.move_list.helpers({
assets() {
return Assets.find({}, { sort: sortFunction });
}
});
And here is the comparator function:
const sortFunction = function (doc1, doc2) {
const barcodes = Session.get('barcodesArray');
if (barcodes.indexOf(doc1.barcode) === -1 || barcodes.indexOf(doc2.barcode) === -1) {
return 0;
}
let last = null;
_.each(barcodes, function (barcode) {
if (barcode === doc1.barcode) last = doc1.barcode;
if (barcode === doc2.barcode) last = doc2.barcode;
});
return last === doc1.barcode ? 1 : -1;
}
Error
When the page loads, the following error is returned:
Exception in template helper: Error: Match error: Failed Match.OneOf, Match.Maybe or Match.Optional validation
I put a breakpoint in chrome into the sortFunction, however the function was never entered and the breakpoint never reached.
Of course, the error is not throw when I remove sort.
References
This feature is not very well documented, however here is the relevant part of the docs:
For local collections you can pass a comparator function which receives two document objects, and returns -1 if the first document comes first in order, 1 if the second document comes first, or 0 if neither document comes before the other. This is a Minimongo extension to MongoDB.
And the commit by mitar adding the functionality, with example code from the test:
var sortFunction = function (doc1, doc2) {
return doc2.a - doc1.a;
};
c.find({}, {sort: sortFunction})
Can anyone make sense of this error?
Edit:
This issue should be resolved in Meteor >= v1.3.3.1.
Local collections (i.e, client-side and in-memory server-side collections) will allow to pass a function as the sort clause.
The error comes from the mongo package, where the spec does not allow sort to be a function.
#mitar changed LocalCollection in the minimongo package. LocalCollection is part of the Mongo.Collection object on the client (its _collection attribute), but queries are still checked according to the original mongo spec. I believe this to be a bug, as the spec was not updated to reflect the change.
To overcome this (in the meantime), either have the function accept a sub-field, such that the sort value is an object:
var sortFunction = function (x, y) {
return x - y;
};
c.find({}, {sort: {a: sortFunction}});
or use the c._collection.find() instead, which will work (as far as I can tell), except it will not apply any transformations defined for the collection.
var sortFunction = function (doc1, doc2) {
return doc2.a - doc1.a;
};
c._collection.find({}, {sort: sortFunction});
Is there a way to get a callback for when an entire list renders?
I've tried
Template.articles.rendered = function() {
var lastChapter = Chapters.findOne({}, {
sort: {
createdTime: -1
}
})
if (lastChapter._id != this.data._id)
return
doSomething()
};
But this is unreliable because chapters are added 1 by 1 instead of all at once, so this actually fires multiple times.
Thanks.
rendered is called when a part of the template is re-rendered, so you should check inside your rendered method whether you want to do anything now. When does "the entire list renders" happen? You know that in your code, for instance by checking if the list is of an expected length yet.
In as3, I am adding event listener and then attaching the anonymous function to it:
myBox.addEventListener(MouseEvent.ROLL_OVER,
function(e:MouseEvent):void
{
Alert.show(count, 'Alert Box');
);
Now this whole piece of code is looped n times. Now, I have n myBoxes and whenever I roll my mouse over the box, it should alert the name. But, what I see is that the last value of count is used by each box.
How can I pass parameter or value to the anonymous function? ( as roll over , I believe, expects only one variable)
You need to create a new scope by executing a function:
for (var i:int = 0; i<10; i++)
{
(function(count:int):void
{
myBox.addEventListener(MouseEvent.ROLL_OVER,
function(e:MouseEvent):void { Alert.show(count, 'Alert Box'); });
})(i);
}
Rather than relying on an index, wouldn't it be simpler (and better) to get the currentTarget of the event and get the value of one of its members?
myBox.addEventListener(MouseEvent.ROLL_OVER,
function(e:MouseEvent):void
{
Alert.show(UIComponent(e.currentTarget).name, 'Alert Box');
);
If you absolutely have to reference the index, you could get that by
UIComponent(e.currentTarget).parent.getChildIndex(e.currentTarget)
And, now that I think of it, you don't even have to make this an anonymous function at all if you use the event model.