Durandal - Correct way to disable .canDeactivate for 'Success' operations? - durandal

I have an edit page (in a DurandalJS single page app), where I use the .canDeactivate lifecycle method to check if there are any changes to the record, and optionally prompt them for confirmation before leaving the page.
I also have a 'Save' and 'View History' button. Is the correct thing to do to override the .canDeactivate method before calling router.navigate, to stop the modal popup invoking?
E.g.: As here:
self.onSave = function() {
self.repository.updateItem(self.model).done(function() {
self.canDeactivate = null; // Is this the correct way to do this?
router.navigate("#/home");
}
}
As this .canDeactivate will otherwise get called:
self.canDeactivate = function() {
if (!self.model.hasChanges()) {
return true;
}
return app.ShowMessage("Unsaved data will be lost", "Are you sure you wish to exit?", ["Yes", "No"]).done(function(result) {
return result !== "No";
}
};

Why dont you just set
self.model.hasChanges(false)
in your updateItem callback?
Then when your canDeactivate is called, it will return true.
Also you seem to have an error in your ShowMessage callback. I think you mean to do:
return result != "No";

I don't think the way Durandal decides whether to attempt to call a canDeactivate function is fully defined, other than the fact that if it's not in the view model, it won't try. Hence, even if it works as is, a future version of the framework could change its check to something like if (canDeactivate in viewModel) viewModel.canDeactivate(...); without further tests, and your code would break.
This is unlikely, but if you want to worry about it, you should thus delete self.canDeactivate instead of assigning it the null value.
Quote from the documentation:
To participate in the lifecycle, implement any (or none) of the
functions below on the object that you set the activator to (...)
Current implementation (activator.js, L126, 1eecbc2d3f84dc42eb7304bde761d88f300d8951):
if (item && item.canDeactivate) {
So it only checks if it's truthy (which would indicate using null works fine currently, too).
If you want to discuss the pattern, I don't see anything wrong with it, as long as it makes sense to you and everyone who should read the code.
You're not supposed to be activating and deactivating views programmatically in any critical path, so performance should be irrelevant either way (flag on view model or deletion of canDeactivate).

Related

Office UI Fabric - PeoplePicker: Cannot get createGenericItem to work

Perhaps it's just a misunderstanding on my side, but I thought the callback for createGenericItem in the PeoplePicker (https://developer.microsoft.com/en-us/fabric#/components/peoplepicker) was used to handle input, that cannot be matched to any of the available items, and then give the possibility to create an adhoc item for this. But, whatever I tried, the callback is never called.
I made a simple pen here for the issue: https://codepen.io/anon/pen/daGPWe?editors=0010
In the example, there are two items, Peter and Maria. If you type something different (and hit enter, tab, space, whatever) I'd expect the createGenericItem callback to be called, but it isn't.
What am I doing wrong? Or is there a misunderstanding of the purpose of this callback? I'm unable to find an example anywhere.
Regarding
but I thought the callback for createGenericItem in the PeoplePicker
(https://developer.microsoft.com/en-us/fabric#/components/peoplepicker)
was used to handle input
that's correct. In order to trigger IBasePickerProps.createGenericItem function, IBasePickerProps.onValidateInput function needs to be provided with ValidationState.valid as a return value, for example:
<NormalPeoplePicker
createGenericItem={this.createGenericItem}
onValidateInput={this.handleValidateInput}
selectedItems={this.state.selectedItems}
onResolveSuggestions={this.handleResolveSuggestions}
onChange={this.handleChange}
/>
private handleValidateInput(input: string) {
return ValidationState.valid;
}
private createGenericItem(input: string, validationState: ValidationState) {
return { text: "Unknown person", state: validationState };
}
This demo demonstrates it, once tab or enter key is clicked and value cannot be resolved to any of the available items, Unknown person item is getting displayed

How to use store.filter / store.find with Ember-Data to implement infinite scrolling?

This was originally posted on discuss.emberjs.com. See:
http://discuss.emberjs.com/t/what-is-the-proper-use-of-store-filter-store-find-for-infinite-scrolling/3798/2
but that site seems to get worse and worse as far as quality of content these days so I'm hoping StackOverflow can rescue me.
Intent: Build a page in ember with ember-data implementing infinite scrolling.
Background Knowledge: Based on the emberjs.com api docs on ember-data, specifically the store.filter and store.find methods ( see: http://emberjs.com/api/data/classes/DS.Store.html#method_filter ) I should be able to set the model hook of a route to the promise of a store filter operation. The response of the promise should be a filtered record array which is a an array of items from the store filtered by a filter function which is suppose to be constantly updated whenever new items are pushed into the store. By combining this with the store.find method which will push items into the store, the filteredRecordArray should automatically update with the new items thus updating the model and resulting in new items showing on the page.
For instance, assume we have a Questions Route, Controller and a model of type Question.
App.QuestionsRoute = Ember.Route.extend({
model: function (urlParams) {
return this.get('store').filter('question', function (q) {
return true;
});
}
});
Then we have a controller with some method that will call store.find, this could be triggered by some event/action whether it be detecting scroll events or the user explicitly clicking to load more, regardless this method would be called to load more questions.
Example:
App.QuestionsController = Ember.ArrayController.extend({
...
loadMore: function (offset) {
return this.get('store').find('question', { skip: currentOffset});
}
...
});
And the template to render the items:
...
{{#each question in controller}}
{{question.title}}
{{/each}}
...
Notice, that with this method we do NOT have to add a function to the store.find promise which explicitly calls this.get('model').pushObjects(questions); In fact, trying to do that once you have already returned a filter record array to the model does not work. Either we manage the content of the model manually, or we let ember-data do the work and I would very much like to let Ember-data do the work.
This is is a very clean API; however, it does not seem to work they way I've written it. Based on the documentation I cannot see anything wrong.
Using the Ember-Inspector tool from chrome I can see that the new questions from the second find call are loaded into the store under the 'question' type but the page does not refresh until I change routes and come back. It seems like the is simply a problem with observers, which made me think that this would be a bug in Ember-Data, but I didn't want to jump to conclusions like that until I asked to see if I'm using Ember-Data as intended.
If someone doesn't know exactly what is wrong but knows how to use store.push/pushMany to recreate this scenario in a jsbin that would also help too. I'm just not familiar with how to use the lower level methods on the store.
Help is much appreciated.
I just made this pattern work for myself, but in the "traditional" way, i.e. without using store.filter().
I managed the "loadMore" part in the router itself :
actions: {
loadMore: function () {
var model = this.controller.get('model'), route = this;
if (!this.get('loading')) {
this.set('loading', true);
this.store.find('question', {offset: model.get('length')}).then(function (records) {
model.addObjects(records);
route.set('loading', false);
});
}
}
}
Since you already tried the traditional way (from what I see in your post on discuss), it seems that the key part is to use addObjects() instead of pushObjects() as you did.
For the records, here is the relevant part of my view to trigger the loadMore action:
didInsertElement: function() {
var controller = this.get('controller');
$(window).on('scroll', function() {
if ($(window).scrollTop() > $(document).height() - ($(window).height()*2)) {
controller.send('loadMore');
}
});
},
willDestroyElement: function() {
$(window).off('scroll');
}
I am now looking to move the loading property to the controller so that I get a nice loader for the user.

Backbone: how to test preventDefault to be called without testing directly the callback

Let's say we have a simple Backbone View, like this:
class MyView extends Backbone.View
events:
'click .save': 'onSave'
onSave: (event) ->
event.preventDefault()
# do something interesting
I want to test that event.preventDefault() gets called when I click on my element with the .save class.
I could test the implementation of my callback function, pretty much like this (Mocha + Sinon.js):
it 'prevents default submission', ->
myView.onSave()
myView.args[0][0].preventDefault.called.should.be.true
I don't think it's working but this is only to get the idea; writing the proper code, this works. My problem here is that this way I'm testing the implementation and not the functionality.
So, my question really is: how can I verify , supposing to trigger a click event on my .save element?
it 'prevents default submission', ->
myView.$('.save').click()
# assertion here ??
Thanks as always :)
Try adding a listener on the view's $el, then triggering click on .save, then verify the event hasn't bubbled up to the view's element.
var view = new MyView();
var called = false;
function callback() { called = true; }
view.render();
// Attach a listener on the view's element
view.$el.on('click', callback);
// Test
view.$('.save').trigger('click');
// Verify
expect(called).toBeFalsy();
So you want to test that preventDefault is called when a click event is generated, correct?
Couldn't you do something like (in JavaScript. I'll leave the CoffeeScript as an exercise ;)):
var preventDefaultSpy;
before(function() {
preventDefaultSpy = sinon.spy(Event.prototype, 'preventDefault');
});
after(function() {
preventDefaultSpy.restore();
});
it('should call "preventDefault"', function() {
myView.$('.save').click();
expect(preventDefaultSpy.callCount).to.equal(1);
});
You might want to call preventDefaultSpy.reset() just before creating the click event so the call count is not affected by other things going on.
I haven't tested it, but I believe it would work.
edit: in other words, since my answer is not that different from a part of your question: I think your first approach is ok. By spying on Event.prototype you don't call myView so it's acting more as a black box, which might alleviate some of your concerns.

How do you recover the dijit registry after destroying it recursively?

I am working on an application and was doing something like this:
dojo.ready(
function(){ require['dojo/parser','dijit/registry','dojo/on'],function(.....){
//find a dijit and wrap it in event handling code.});
I was getting an error indicating that dojo was trying to register a widget with an id that was already in use. To solve the problem I entered this line of code:
//before finding the dijit destroy the existing registry.
However, logically this prevents the next line from working because now no widget exists to which I can connect an event. How can I recover the dijit ids?
The best solution is to find out why your code is trying to register a widget with an id that is already in use and change it to not to do so.
The #mschr's solution should work, but I would advise again using it, as it can break your code in many other places and you are likely to spend hours investigating strange behavior of your application.
Anyway, if you are willing to do it that way and automatically destroy widgets with the same ID, do not override registry.add() method. You could do it, but it does not mean, you should do it (especially in programming). Employ dojo/aspect instead to call a function that will destroy the widget with the same ID before registry.add() is called:
require([
"dojo/aspect",
"dijit/registry"
], function(
aspect,
registry
) {
aspect.before(registry, "add", function(widget) {
if(registry.byId(widget.id)) {
registry.byId(widget.id).destroy();
// this warning can save you hours of debugging:
console.warn("Widget with id==" + widget.id + " was destroyed to register a widget with the same id.");
}
return [widget];
});
});
I was myself curious how to accomplish #mschr solution without that override, so I created an jsFiddle to experiment: http://jsfiddle.net/phusick/feXVT/
What happens once you register a dijit is the following; it is referenced by the dijit.registry._hash:
function (widget) {
if (hash[widget.id]) {
throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered");
}
hash[widget.id] = widget;
this.length++;
}
Now, every now and then you would have a contentpane in which you would put a widget programatically (programatically, hence dojo.parser handles cpane.unload and derefences / destroys parser-instantiated widgets).
When this happens, you need to hook onto some form of 'unload', like, when your call cpane.set('content' foo) or cpane.set('href', bar). Hook is needed to destroy and unregister the instances you keep of widgets - otherwise you would have a memoryleak in your program.
Normally, once an object has no references anywhere - it will get cleaned out of memory however with complex objects such as a widget might be, 'class-variables' often have reference to something _outside _widget scope which flags the widget unsafe to delete to the garbage collector... Once you get this point, you will know to perform proper lifecycles, yet not before the concept is fully understood..
What you could do is to override the dijit.registry with your own handler and have any widgets that are doublets destroyed automatically like so:
// pull in registry in-sync and with global scoped
// accees (aka dijit.registry instead of dj_reg)
require({
async:false,
publishRequireResult:true
}, [
"dijit.registry"
], function(dj_reg) {
dijit.registry.add = function(widget) {
// lets change this bit
if (this._hash[widget.id]) {
this._hash[widget.id].destroy(); // optinally destroyRecursively
this.remove(widget.id)
}
this._hash[widget.id] = widget;
this.length++;
}
});

Cant handle it (jQuery hanlder understanding needed)

I'm embarrassed to even ask BUT could someone help me understand what a "handler" is. I am new to jQuery and the API constantly has references similar to the following:
toggle( handler(eventObject), handler(eventObject), [ handler(eventObject) ] )
I scratch my head and say to myself "what the hell is a handler". Then I check my 2 jquery books and don't really see anything specific there. I get what an event handler does, it handles an event. But the word handler in the above context confuses me including "eventObject". I tried to google it but could not really find a really clear definition of what exactly a handler is as it relates to jquery. Thanks for your help =]
Handlers are any functions that you write to handle events. For e.g. in
$(document).ready(function() {
//......
});
the handler is
function() {
//.......
}
Think of a handler as a callback for whatever operation is being invoked. In the case of handler(eventObject) it means that the method with that parameter can accept a function being passed to it and that function will be called at some specific point in time before, during, or after the execution of the method receiving it (as indicated by the parameter specification) and it will be passed a value called eventObject which can be anything, but is most likely the target of the given event your callback is being issued for.
Here's an example:
function MyCallback(eventObject) {
alert(jQuery(eventObject).attr('id') + ' toggled'));
}
jQuery("#myBtn").click(function() {
jQuery("#myObj").toggle("fast", function(eventObject) { MyCallback(eventObject); });
});
With the above code, when #myBtn is clicked the element #myObj will be toggled (fast) and as soon as the toggle animation completes MyCallback will be called and passed #myObj which will cause an alert to appear saying, "myObj toggled".
This is the function which will handle the event. To expand, in the case of toggle, ON calls the first function (with the eventObject) and OFF calls the second function. eventObject will hold different info depending on events, like coordinates of the mouse.