Register an Event inside an XTemplate - Sencha Touch 2? - javascript

I'm trying to set up my controller to respond to Ext.XTemplate events and avoid the mess of writing a bunch of hacked javascript.
Unfortunately, I can't find a way to either 1) log a click event or 2) use componentQuery to register the XTemplate.
Here's a basic controller config:
config: {
refs: {
reportChooser: 'xtemplate[id=jobReportChooser]'
},
control: {
reportChooser: {
tap: 'onAnonymousTap'
}
}
},
Is there a way to do it in the controller OR at least clean up the listeners?

I couldn't find a direct reference since XTemplate has no built-in Events, but here's a nice workaround that's a little cleaner.
In your view initialize method
initialize: function() {
this.element.on({
tap: function(e, dom) {
var el = Ext.get(e.target),
elParent = Ext.get(e.parent),
fireEvent;
window.jobAction = this;
window.jobEl = el;
if (el.hasCls('job-start') || el.parent().hasCls('job-start')) {
fireEvent = 'start';
} else if (el.hasCls('job-hold') || el.parent().hasCls('job-hold')) {
fireEvent = 'hold';
} else if (el.hasCls('job-report') || el.parent().hasCls('job-report')) {
Ext.Viewport.setMasked({ xtype: 'loadmask' });
var teamId = APPNAME.currentItem.data.teamId;
var jobId = APPNAME.currentItem.data.jobId;
APPNAME.app.dispatch({controller: 'Jobs', action: 'displayReportChooser', args:[teamId,'job',jobId]})
}
if (fireEvent) {
Ext.Viewport.setMasked({ xtype: 'loadmask' });
this.fireEvent(fireEvent, Alloy.currentJob, el);
}
},
delegate: '.job-info',
scope: this
});
}
What it means
Use the application dispatch method. It will make life a lot easier and help you still organize actions inside the controller.
http://docs.sencha.com/touch/2-0/#!/api/Ext.app.Application-method-dispatch

Related

How to access a backbone view in another file from backbone router

I have a backbone application which works fine but was getting a bit heavy to be in one file so I have started to separate it into different files I now have:
backbone-view.js
backbone-router.js
...
I am using my backbone router to instantiate views when the URL changes like so:
var Router = Backbone.Router.extend({
routes: {
'our-approach.php': 'instantiateOurApproach',
'our-work.php': 'instantiateOurWork',
'who-we-are.php': 'instantiateWhoWeAre',
'social-stream.php': 'instantiateSocialStream',
'contact.php': 'instantiateContact'
},
instantiateOurApproach: function() {
if(window.our_approach_view == null) {
window.our_approach_view = new OurApproachView();
}
},
instantiateOurWork: function() {
if(window.our_work_view == null) {
window.our_work_view = new OurWorkView();
}
},
instantiateWhoWeAre: function() {
if(window.who_we_are_view == null) {
window.who_we_are_view = new WhoWeAreView();
}
},
instantiateSocialStream: function() {
if(window.social_stream_view == null) {
window.social_stream_view = new SocialStreamView();
}
},
instantiateContact: function() {
if(window.contact_view == null) {
window.contact_view = new ContactView();
}
}
});
The problem I am now having is that I cannot access the views as they are declared in a separate file so the following are all undefined:
OurApproachView()
OurWorkView()
WhoWeAreView()
SocialStreamView()
ContactView()
I have tried doing:
window.OurApproachView()
But this doesn't work.
I am not sure what my next move is so if anyone can help that would be awesome.
Thanks!
EDIT
OK so it seems doing:
window.OurApproachView()
does actually work, my apologies there, but does anyone have a better suggestion?
You can take this approach:
// sample-view.js
var app = app || {};
$(function() {
app.SampleView = Backbone.View.extend({
el: '#sample-element',
template : // your template
events: {
// your events
},
initialize: function() {
// do stuff on initialize
},
render: function() {
// do stuff on render
}
});
});
Similarly, all your js files (models, collections, routers) can be setup like this. You would then be able to access any view from the router by doing:
var view = new app.SampleView();
This works:
window.our_work_view = new window.OurApproachView();
But I don't like it as a solution.
Anyone suggest anything better?

Reusing a modal template

On my current project, there are starting to be a few views that are modal views that are being used to delete items on the site. They are currently generic in that it's just a text description of the item they are deleting. Maybe in the future there will be an icon or a short description as well. There are now tasks to have that functionality to delete other stuff on our site. I'm new to the web, MVC, asp.net, etc, and what I want to know is if it's better to reuse our current modal view somehow, and pass in the objects we need to show in the view. Because the view needs to send the url back to the server on which items to delete, that part of code would need to be different for the view as well. Here is some of the stuff in our view along with a .cshtml template that's pretty generic that I didn't include.
Views.DeleteGiftModal = (function () {
return Backbone.View.extend({
template: Templates["template-gift-delete-modal"],
tagName: 'div',
initialize: function (options) {
$(window).bind("disposeModal", _.bind(this.disposeModal, this));
_.bindAll(this, "showDialog", "disposeModal", "displayResults");
this.eventAggregator = options.eventAggregator;
this.itemsToDelete = options.model;
this.errors = {};
this.render();
return this;
},
events: {
"click #delete-btn": "deleteItems",
"click #ok-btn": "disposeModal",
"click #cancel-btn": "disposeModal"
},
disposeModal: function (event, refresh) {
this.$el.modal("hide");
if (event != null && event.currentTarget != null && event.currentTarget.id == 'ok-btn')
refresh = true;
this.trigger("modalClosed", refresh);
this.remove();
this.unbind();
},
showDialog: function () {
this.$el.modal("show");
},
deleteItems: function () {
var self = this;
var element = this.$el;
var numberGifts = this.getKeys(this.itemsToDelete).length;
this.results = [];
var hasError = false;
element.find("#actions").hide();
element.find("#ok-actions").show();
$.each(this.itemsToDelete, function(i, v) {
// tell model to go away
var gift = new Gift({ id: i });
gift.destroy({
success: function (model, response) {
self.results.push({ id: model.id, response: response });
numberGifts--;
if (numberGifts <= 0) {
if (!hasError) {
self.disposeModal(null, true);
} else {
self.displayResults();
}
}
}
});
});
},
displayResults: function () {
var element = this.$el;
$.each(this.results, function(i, v) {
// to do check response for error message
var list = element.find("#delete-item-" + v.id);
if (v.response.message == "Deleted") {
list.append(" - <span align='right' style='color: green'>Deleted</span>");
} else {
hasError = true;
list.append(" - <span align='right' style='color: red'>" + v.response.message + "</span>");
}
});
},
render: function () {
this.$el.append(this.template);
this.$el.find("#ok-actions").hide();
// show list of item names
var list = this.$el.find("#items-to-delete-list");
$.each(this.itemsToDelete, function (i, v) {
$("<li id='delete-item-" + i + "'>" + v.name + "</li>").appendTo(list);
});
this.$el.attr('id', 'delete-gift-dialog');
return this;
}
});
})();
As I am looking through the code, and this being my first real project, it seems like a lot of things that could be quite similar, like deleting a Gift, deleting a Toy, etc have different Controllers for each (GiftController, ToyController), and hit different URLs. So currently things are all in their own class like that. I was wondering if that's the more standard way to approach these types of problems as well with views. Thanks in advance!
The app we're developing at work had a similar issue. We're using Backbone too so I can completely relate to this. What I ended up doing is have a sort of ModalBuilder that builds a form in a modal for you and binds events on the form elements for submit. The initialization of it could look like this:
new ModalBuilder({
form: [
{
tag: 'select[name="id"]',
options: [
{ name: 'Item 1', id: 12 },
{ name: 'Item 2', id: 32 }
]
},
{
tag: 'input[type="submit"]',
value: 'Delete'
}
],
events: function(){
$('input[type="submit"]').on('click', function(){
// Delete via ajax
})
}
})
What we do is we have different templates for every form element, inputfields and textareas and so on and we reuse it all over the place. ModalBuilder takes these arguments and builds a form
Also for certain cases it might be better to render the form server-side and deliver it to your modal via ajax. You have to weigh what makes your app more performant I suppose.

Adding a jQuery style event handler of iPhone OS events

I'm looking for a super simple jQuery extension. Basically I need to use some events that jQuery does not explicitly support. These events are the iPhone touch events like ontouchstart, ontouchend, and ontouchmove.
I have it working via this:
// Sucks
$('.clickable').each(function() {
this.ontouchstart = function(event) {
//do stuff...
};
}
Which kind of sucks and is unjqueryish. Here is what I would like:
// Better
$('.clickable').touchstart(function() {
//do stuff...
}
Or even better with 1.4
// Awesome
$('.clickable').live('touchstart', function() {
//.. do stuff
}
These events need no special handling and should work just like any other events, but I can't seem to figure out how to extend jquery to make them work just like all the other events do.
I wrote the plugin, if the user does have touch available, use, otherwise, call click
jQuery.event.special.tabOrClick = {
setup: function (data, namespaces) {
var elem = this, $elem = jQuery(elem);
if (window.Touch) {
$elem.bind('touchstart', jQuery.event.special.tabOrClick.onTouchStart);
$elem.bind('touchmove', jQuery.event.special.tabOrClick.onTouchMove);
$elem.bind('touchend', jQuery.event.special.tabOrClick.onTouchEnd);
} else {
$elem.bind('click', jQuery.event.special.tabOrClick.click);
}
},
click: function (event) {
event.type = "tabOrClick";
jQuery.event.handle.apply(this, arguments);
},
teardown: function (namespaces) {
var elem = this, $elem = jQuery(elem);
if (window.Touch) {
$elem.unbind('touchstart', jQuery.event.special.tabOrClick.onTouchStart);
$elem.unbind('touchmove', jQuery.event.special.tabOrClick.onTouchMove);
$elem.unbind('touchend', jQuery.event.special.tabOrClick.onTouchEnd);
} else {
$elem.unbind('click', jQuery.event.special.tabOrClick.click);
}
},
onTouchStart: function (e) {
this.moved = false;
},
onTouchMove: function (e) {
this.moved = true;
},
onTouchEnd: function (event) {
if (!this.moved) {
event.type = "tabOrClick";
jQuery.event.handle.apply(this, arguments)
}
}
};
$("#xpto").bind("tabOrClick", function () {
alert("aaaa");
});
I've made a small update to Alexandre's plugin to include Android support. Android's browser does not currently support the window.Touch method of detecting touch support.
I love how Alexandre's script waits to ensure movement didn't occur to prevent triggering the event when the user swipes to scroll across the screen. However a downfall of that approach is that it causes its own delay by waiting for the user to lift their finger off of the screen before triggering. I've updated his plugin to include a "touchactive" class that gets applied to items that a user is currently touching. If you take advantage of that class you can provide immediate visual feedback to users without causing an actual event to get triggered until after movement check has completed.
jQuery.event.special.touchclick = {
setup: function (data, namespaces) {
var elem = this, $elem = jQuery(elem);
var ua = navigator.userAgent.toLowerCase();
var isAndroid = ua.indexOf("android") > -1;
if (window.Touch || isAndroid) {
$elem.bind('touchstart', jQuery.event.special.touchclick.onTouchStart);
$elem.bind('touchmove', jQuery.event.special.touchclick.onTouchMove);
$elem.bind('touchend', jQuery.event.special.touchclick.onTouchEnd);
} else {
$elem.bind('click', jQuery.event.special.touchclick.click);
}
},
click: function (event) {
event.type = "touchclick";
jQuery.event.handle.apply(this, arguments);
},
teardown: function (namespaces) {
var elem = this, $elem = jQuery(elem);
if (window.Touch) {
$elem.unbind('touchstart', jQuery.event.special.touchclick.onTouchStart);
$elem.unbind('touchmove', jQuery.event.special.touchclick.onTouchMove);
$elem.unbind('touchend', jQuery.event.special.touchclick.onTouchEnd);
} else {
$elem.unbind('click', jQuery.event.special.touchclick.click);
}
},
onTouchStart: function (e) {
this.moved = false;
$(this).addClass('touchactive');
},
onTouchMove: function (e) {
this.moved = true;
$(this).removeClass('touchactive');
},
onTouchEnd: function (event) {
if (!this.moved) {
event.type = "touchclick";
jQuery.event.handle.apply(this, arguments)
}
$(this).removeClass('touchactive');
}
};
I've also posted this to github in case there are further caveats that are discovered https://github.com/tuxracer/jquery-touchclick
This now works, just like it's stubbed out above, on the latest jQuery release. Go jQuery!
Here's a start:
$.fn.touchstart = function(fn) { return this[fn ? "bind" : "trigger"]("touchstart", fn); };
$.event.special.touchstart = {
setup: function() {
$.event.add(this, "mouseenter", extendedClickHandler, {});
},
teardown: function() {
$.event.remove(this, "mouseenter", extendedClickHandler);
}
};
Where extendedClickHandler is the function that does what it's suppose to do.
More info here: http://brandonaaron.net/blog/2009/03/26/special-events
jQuery.com is a great source of information like this.
If you build your own plugin you'll be able to use whatever naming you like on your method calls.

Delaying default events in Javascript

I would like to be able to delay the default action of an event until some other action has been taken.
What it's for: I'm trying to build a reusable, unobtrusive way to confirm actions with a modal-type dialogue. The key wishlist item is that any Javascript handlers are attached by a script, and not written directly inline.
To make this truly reusable, I want to use it on different types of items: html links, checkboxes, and even other Javascript-driven actions. And for purely HTML elements like links or checkboxes, I want them to degrade gracefully so they're usable without Javascript turned on.
Here's how I would envision the implementation:
Some Link
_________
<script>
attachEvent('a.confirm','click', confirmAction.fire)
var confirmAction = (function(){
var public = {
fire: function(e){
e.default.suspend();
this.modal();
},
modal: function(){
showmodal();
yesbutton.onclick = this.confirmed;
nobutton.onclick = this.canceled;
},
confirmed: function(){
hidemodal();
e.default.resume();
},
canceled: function(){
hidemodal();
e.default.prevent();
}
}
return public;
})()
</script>
I know about the e.preventDefault function, but that will kill the default action without giving me the ability to resume it. Obviously, the default object with the suspend, resume and prevent methods is made up to illustrate my desired end.
By the way, I'm building this using the Ext.Core library, if that helps. The library provides a good deal of normalization for handling events. But I'm really very interested in learning the general principles of this in Javascript.
To resume, you could try saving the event and re-fire it, setting a flag that can be used to skip the handlers that call suspend() ('confirmAction.fire', in your example).
Some Link
_________
<script>
function bindMethod(self, method) {
return function() {
method.apply(self, arguments);
}
}
var confirmAction = (function(){
var public = {
delayEvent: function(e) {
if (e.suspend()) {
this.rememberEvent(e);
return true;
} else {
return false;
}
},
fire: function(e){
if (this.delayEvent(e)) {
this.modal();
}
},
modal: function(){
showmodal();
yesbutton.onclick = bindMethod(this, this.confirmed);
nobutton.onclick = bindMethod(this, this.canceled);
},
confirmed: function(){
hidemodal();
this.rememberEvent().resume();
},
canceled: function(){
hidemodal();
this.forgetEvent();
},
rememberEvent: function (e) {
if (e) {
this.currEvent=e;
} else {
return this.currEvent;
}
},
forgetEvent: function () {
delete this.currEvent;
}
}
return public;
})()
// delayableEvent should be mixed in to the event class.
var delayableEvent = (function (){
return {
suspend: function() {
if (this.suspended) {
return false;
} else {
this.suspended = true;
this.preventDefault();
return true;
}
},
resume: function () {
/* rest of 'resume' is highly dependent on event implementation,
but might look like */
this.target.fireEvent(this);
}
};
})();
/* 'this' in event handlers will generally be the element the listener is registered
* on, so you need to make sure 'this' has the confirmAction methods.
*/
mixin('a.confirm', confirmAction);
attachEvent('a.confirm','click', confirmAction.fire);
</script>
This still has potential bugs, such as how it interacts with other event listeners.

Memento in Javascript

I'm looking for a JavaScript implementation of the memento pattern (GoF) to be used in CRUD forms. In its basic level it will be enough to undo changes on inputs, but it would be great to use it with standard JS frameworks like YUI or Ext, to undo & redo grid actions ( new row, delete row etc.).
Thanks
Since I'm not seeing any code examples, here is a quick 'n Dirty implementation of undo for an EXT form:
var FormChangeHistory = function(){
this.commands = [];
this.index=-1;
}
FormChangeHistory.prototype.add = function(field, newValue, oldValue){
//remove after current
if (this.index > -1 ) {
this.commands = this.commands.slice(0,this.index+1)
} else {
this.commands = []
}
//add the new command
this.commands.push({
field:field,
before:oldValue,
after:newValue
})
++this.index
}
FormChangeHistory.prototype.undo = function(){
if (this.index == -1) return;
var c = this.commands[this.index];
c.field.setValue(c.before);
--this.index
}
FormChangeHistory.prototype.redo = function(){
if (this.index +1 == this.commands.length) return;
++this.index
var c = this.commands[this.index];
c.field.setValue(c.after);
}
Ext.onReady(function(){
new Ext.Viewport({
layout:"fit",
items:[{
xtype:"form",
id:"test_form",
frame:true,
changeHistory:new FormChangeHistory("test_form"),
defaults:{
listeners:{
change:function( field, newValue, oldValue){
var form = Ext.getCmp("test_form")
form.changeHistory.add(field, newValue, oldValue)
}
}
},
items:[{
fieldLabel:"type some stuff",
xtype:"textfield"
},{
fieldLabel:"then click in here",
xtype:"textfield"
}],
buttons:[{
text:"Undo",
handler:function(){
var form = Ext.getCmp("test_form")
form.changeHistory.undo();
}
},{
text:"Redo",
handler:function(){
var form = Ext.getCmp("test_form")
form.changeHistory.redo();
}
}]
}]
})
});
Implementing this for an editable grid is a little trickier, but you should be able to make a GridChangeHistory that does the same thing and then call the add() function from EditorGrid's AfterEdit listener.
The "before" and "after" properties could be callback functions which allow you undo/redo any kind of command, but that would require more work when calling add()
Since you are trying to undo/redo commands, I suggest using the Command pattern instead. Here is a link to a tutorial; it's in C#, but it should be simple enough to follow for an OO programmer.

Categories