I have a Backbone View with events for mobile and desktop version:
events: {
"touchstart .button": "action1",
"mousedown .button": "action1",
"touchend .button": "action2",
"mouseup .button": "action2",
}
I want only one of the event fired when touched from my mobile device. Currently in one of my mobile device, action1 is fired twice.
I have tried using a custom method from this solution, but somehow in Android 2.3 the custom method for touchend after touchstart won't be fired, but using pure "touchend" would be fired instead.
Thus I'm thinking if Backbone.js can prevent "mouseup" from being fired if "touchend" is being fired.
Another approach, which feels slightly less hacky to me:
I like to test for touch events and set a global variable upon initialization of the application.
MyApp.clickEvent = ('ontouchstart' in window) ? "touchstart" : "mousedown";
Then in my views I perform something like:
events: function() {
var _events = {};
_events[MyApp.clickEvent + " .button"] = "action1";
return _events;
}
One hacky solution would be to use the underscore.js function throttle to make it so that the function can be called only once per x milliseconds. Check the documentation here to see if it suits your needs.
The final solution would be:
action1: _.throttle(function(params) {
// Do what you do here
}, 400); // The timeframe within which the function can be called just once.
You can define a function to determine touch devices like:
function is_touch_device() {
try {
document.createEvent("TouchEvent");
return true;
} catch (e) {
return false;
}
}
this function is from another thread What's the best way to detect a 'touch screen' device using JavaScript?
then use it in events:
events: function() {
if(is_touch_device) {
return {
/// touch events
}
} else {
return {
/// mouse events
}
}
}
if they are exactly the same dehrg answer would be a less redundant one
Related
I have a proxy store that fills through two different ways - either via loadData, or via load.
I have certain actions that should take place once the store is filled; namely, a certain record should be searched and selected:
preselectByName:function(name, groupName) {
var store = this;
if(store.preselected) return;
store.preselected = true;
store.on('load',function() {
store.selectByName(name, groupName);
store.preselected = false;
},store,{single:true});
}
called like this:
if(store.isLoaded) store.selectByName(name, groupName);
else store.preselectByName(name, groupName);
This code works fine if the store fills via load, but not via loadData.
Is there another event that is fired from both load and loadRecord?
How else would I implement a listener that fires on any of the two events, whichever comes first?
datachanged (http://docs.sencha.com/extjs/6.2.1/classic/Ext.data.Store.html#event-datachanged)
is an event, that fires from both load and loadRecord, but be careful, it fires from any change that made to the dataset.
Besides: I usually use this to find the event that I need in ExtJs:
Ext.util.Observable.capture(myObj, function(evname) {console.log(evname, arguments);})
It captures all ExtJs events, fired by the myObj component and logs it to the console.
Or you could create a custom event, which you fire manually on load and after loadData, like:
store.on("load",function(){store.fireEvent("myload")})
and
store.loadData(...)
store.fireEvent("myload")
then
store.on("myload",function(){...})
The load event is specifically for reading data from a remote data source. The store can be extended to introduce an event that satisfies your requirements.
/*
#event loadrecords Fires whenever records a loaded into the store.
#param {Ext.data.Model[]} records An array of records
*/
Ext.define('MyApp.data.Store', {
extend: 'Ext.data.Store',
load: function() {
this.callParent(arguments);
this.fireLoadRecords();
return this;
},
fireLoadRecords: function() {
this.fireEvent('loadrecords', this.getRange());
},
loadRecords: function(records, options) {
this.callParent(arguments);
this.fireLoadRecords();
}
});
http://docs.sencha.com/extjs/6.2.0/classic/Ext.data.ProxyStore.html#event-load
I have an AngularJS app using the Angular Material UI framework.
The app has different mechanisms showing dialogs (e.g error and loading spinner) and it would be preferable to only close one specifically chosen in certain scenarios, e.g. when an AJAX request is finished fetching data, I would like my loading spinner to close, but not any error dialog that may be the result of the fetching.
What I can find in documentation and code doesn't agree (though code should win the argument):
Documentation says only the latest can be closed, with an optional response
The code says the latest, a number of latest or all open can be closed, with an optional reason
Example in the documentation says a specific dialog can be closed, with a flag denoting how or why
I have made a demo of my intent, as MCV as possible – these are the highlights:
var dialog = {},
promise = {};
function showDialogs(sourceEvent) {
showDialog(sourceEvent, "one");
showDialog(sourceEvent, "two");
}
function showDialog(sourceEvent, id) {
dialog[id] = $mdDialog.alert({...});
promise[id] = $mdDialog.show(dialog[id]);
promise[id].finally(function() {
dialog[id] = undefined;
});
}
function closeDialogs() {
$mdDialog.hide("Closed all for a reason", {closeAll: true});
}
function closeDialogLatest() {
$mdDialog.hide("Closed from the outside");
}
function closeDialogReason() {
$mdDialog.hide("Closed with a reason");
}
function closeDialogSpecific(id) {
$mdDialog.hide(dialog[id], "finished");
}
EDIT:
I know the code always wins the argument about what happens, but I wasn't entirely sure it was the right code I was looking at.
I have updated the examples to better test and illustrate my point and problem. This shows things to work as the code said.
What I'm really looking for is whether it might still be possible to achieve my goal in some other way that I didn't think of yet.
Using $mdPanel instead of $mdDialog I was able to achieve the desired effect; I forked my demo to reflect the changes – these are the highlights:
var dialog = {};
function showDialogs() {
showDialog("one");
showDialog("two");
}
function showDialog(id) {
var config = {...};
$mdPanel.open(config)
.then(function(panelRef) {
dialog[id] = panelRef;
});
}
function closeDialogs() {
var id;
for(id in dialog) {
closeDialogSpecific(id, "Closed all for a reason");
}
}
function closeDialogSpecific(id, reason) {
var message = reason || "finished: " + id;
if(!dialog.hasOwnProperty(id) || !angular.isObject(dialog[id])) {
return;
}
if(dialog[id] && dialog[id].close) {
dialog[id].close()
.then(function() {
vm.feedback = message;
});
dialog[id] = undefined;
}
}
I would suggest having two or more dialogs up at the same time isn't ideal and probably not recommended by Google Material design.
To quote from the docs
Use dialogs sparingly because they are interruptive.
You say:
when an AJAX request is finished fetching data, I would like my
loading spinner to close, but not any error dialog that may be the
result of the fetching.
My solution here would be to have one dialog which initially shows the spinner. Once the request is finished replace the spinner with any messages.
Im working on an extjs application. We're have a page that is for looking at a particular instance of an object and viewing and editing it's fields.
We're using refs to get hold of bits of view in the controller.
This was working fine, but I've been sharding the controller into smaller pieces to make it more managable and realised that we are relying on a race condition in our code.
The logic is as follows:
Initialise the controller
parse the url to extract the id of the object
put in a call to load the model with the given view.
in the load callback call the controller load method...
The controller load method creates some stores which fire off other requests for bits of information using this id. It then uses some of the refs to get hold of the view and then reconfigures them to use the stores when they load.
If you try and call the controller load method immediately (not in the callback) then it will fail - the ref methods return undefined.
Presumably this is because the view doesnt exist... However we aren't checking for that - we're just relying on the view being loaded by the time the server responds which seems like a recipe for disaster.
So how can we avoid this and be sure that a view is loaded before trying to use it.
I haven't tried rewriting the logic here yet but it looks like the afterrender event probably does what I want.
It seems like waiting for both the return of the store load and afterrender events should produce the correct result.
A nice little abstraction here might be something like this:
yourNamespace.createWaitRunner = function (completionCallback) {
var callback = completionCallback;
var completionRecord = [];
var elements = 0;
function maybeFinish() {
var done = completionRecord.every(function (element) {
return element === true
});
if (done)
completionCallback();
}
return {
getNotifier: function (func) {
func = func || function (){};
var index = elements++;
completionRecord[index] = false;
return function () {
func(arguments);
completionRecord[index] = true;
maybeFinish();
}
}
}
};
You'd use it like this:
//during init
//pass in the function to call when others are done
this.waiter = yourNamespace.createWaitRunner(controller.load);
//in controller
this.control({
'SomeView': {
afterrender: this.waiter.getNotifier
}
});
//when loading record(s)
Ext.ModelManager.getModel('SomeModel').load(id, {
success: this.waiter.getNotifier(function (record, request) {
//do some extra stuff if needs be
me.setRecord(record);
})
});
I haven't actually tried this out yet so it might not be 100% but I think the idea is sound
I am using same el for more than 1 view like below. I'm not facing any problem till now. Is this good approach or should i do any changes?
<div id="app">
<div id="app-header"></div>
<div id="app-container"></div>
<div id="app-footer">
</div>
App View:
{
el: "#app",
v1: new View1(),
v2: new View2(),
render: function () {
if (cond1) {
this.v1.render();
} else if (cond2) {
this.v2.render();
}}
}
View 1:
{
el: "#app-container",
render: function (){
this.$el.html(template);
}
}
View 2:
{
el: "#app-container",
render: function (){
this.$el.html(template);
}
}
By reading your question, I do not really see what advantages you could possibly have using this approach rather than having the different div elements being the root el for your views 1, 2, 3 and using
this.$el.html(template)
in the render method.
Your approach could work for a small application, but I think it will become really hard to maintain as the application grows.
EDIT
I still do not really get your point, you could only initialize everything only once in both cases.
Here is a working Fiddle.
By the way I am changing the content by listening to the click event but this is to simplify the example. It should be done by the router.
I do use a mixin to handle such situation, I call it stated view. For a view with all other options I will send a parameter called 'state', render will in-turn call renderState first time and there after every time I set a 'state' renderState will update the view state. Here is my mixin code looks like.
var setupStateEvents = function (context) {
var stateConfigs = context.getOption('states');
if (!stateConfigs) {
return;
}
var state;
var statedView;
var cleanUpState = function () {
if (statedView) {
statedView.remove();
}
};
var renderState = function (StateView) {
statedView = util.createView({
View: StateView,
model: context.model,
parentEl: context.$('.state-view'),
parentView:context
});
};
context.setState = function (toState) {
if (typeof toState === 'string') {
if (state === toState) {
return;
}
state = toState;
var StateView = stateConfigs[toState];
if (StateView) {
cleanUpState();
renderState(StateView);
} else {
throw new Error('Invalid State');
}
} else {
throw new Error('state should be a string');
}
};
context.getState = function () {
return state;
};
context.removeReferences(function(){
stateConfigs = null;
state=null;
statedView=null;
context=null;
})
};
full code can be seen here
https://github.com/ravihamsa/baseapp/blob/master/js/base/view.js
hope this helps
Backbone Rule:
When you create an instance of a view, it'll bind all events to el if
it was assigned, else view creates and assigns an empty div as el for that view and bind
all events to that view.
In my case, if i assign #app-container to view 1 and view 2 as el and when i initialize both views like below in App View, all events bind to the same container (i.e #app-container)
this.v1 = new App.View1();
this.v2 = new App.View2();
Will it lead to any memory leaks / Zombies?
No way. No way. Because ultimately you are having only one instance for each view. So this won't cause any memory leaks.
Where does it become problematic?
When your app grows, it is very common to use same id for a tag in both views. For example, you may have button with an id btn-save in both view's template. So when you bind btn-save in both views and when you click button in any one the view, it will trigger both views save method.
See this jsFiddle. This'll explain this case.
Can i use same el for both view?
It is up to you. If you avoid binding events based on same id or class name in both views, you won't have any problem. But you can avoid using same id but it's so complex to avoid same class names in both views.
So for me, it looks #Daniel Perez answer is more promising. So i'm going to use his approach.
In my router I require a view like this:
require(['views/business-detail-view'],
function(BusinessDetailView) {
var businessDetailView = new BusinessDetailView({collection: businessOverviewCollection.models[id], id: id});
businessDetailView.render();
}
);
and in the view I'm binding events like this:
events: {
'click #about-btn' : 'aboutHandler',
'click #contact-btn' : 'contactHandler',
'click #deals-btn' : 'dealsHandler',
'click #map-btn' : 'mapHandler'
},
Now the issue is that if the view gets rendered the first times the callbacks are invoked ones. But if the view needs to be rendered again in some other place the callbacks are invoked twice and so on.
How can I prevent this or am I doing something wrong?
UPDATE:
In the meantime I have changed the code in my router to:
if ( !businessDetailView ) {
require(['views/business-detail-view'],
function(BusinessDetailView) {
businessDetailView = new BusinessDetailView({collection: businessOverviewCollection.models[id]});
businessDetailView.render();
}
);
}
else {
businessDetailView.collection = businessOverviewCollection.models[id];
businessDetailView.render();
}
which seem to solve the issue, but I'm still to new to backbone this know whether this is a valid pattern.
At some point in your view you probably clear out the existing HTML on the page and replace it with new HTML. When you clear out old HTML you should also clear out old event handlers so they aren't laying around. For example, in your view when you want to render your newHtml you could do:
#$el.off().html(newHtml)