Can you trigger action/events in Sencha Touch from html elements? - extjs

I have a Sencha tab panel, each tab loads html content via ajax. One of the components is a post/list that visitors can use to drill down once more to read the entire post.
My question is, can I somehow trigger a view switch through the html? Or should I be loading the post data via JSON, and styling a listpanel in Sencha?
Thank you!

You can add listeners to elements within your HTML which would then be able to trigger the view switch. For example,
// simple HTML snippet contained in Panel
<a class="my-link">Click Me!</a>
// on after load/after render (need to ensure that the elements exists in the page!)
// get reference to the containing panel (tab)
var panel = this.items.get(0);
panel.getEl().on({
tap: function(e){
console.log('i was clicked!');
},
delegate: 'a.my-link'
});
The delegate option allows you to pass a selector that means the event will only fire when an element matching that selector is in the event target's chain (i.e. returns something in an e.getTarget(delegate) call).
EDIT
You can access attributes of the tapped element using either the DOM node tapped or use Ext.fly to wrap an Ext.Element instance around it and use the helper methods.
console.log(e.getTarget('a.my-link')); // logs DOM node
console.log(Ext.fly(e.getTarget('a.my-link'))); // logs Ext.Element wrapping DOM node
console.log(e.getTarget('a.my-link').href); // logs href via DOM node property
console.log(Ext.fly(e.getTarget('a.my-link')).getAttribute('href')); // logs href via Ext.Element getAttribute() method
Depending on the nesting you may be able to remove the selector from the getTarget() call (i.e. if you're always tapping on the element your listening on then you can remove it, but if there are children in the element you're listening on then you will need it. In the second case the 'target' will be the child that the event bubbled from so the href etc will be wrong. If that makes sense... :) )

The solution for 2.2.1:
initialize: function() {
this.element.on({
tap: <function>,
delegate: <query_expression>
});
this.callParent(arguments);
}
<query_expression> can be anything fitting in to Ext.query() too.

Related

Registering events works only first time

I have a window, and inside it a panel. The panel contains text (basic html). After the window is ready I call the following function, which finds elements with specific class, and registers click events on them. This works at first.
After closing the window, and recreating 1:1 similar window the events will not fire. The same happens if I .update() the panel and re-run my function - the events fail to fire. Why is this?
I can still see elements being found, and apparently some events must be registered, but my clicks can't be captured by the debug code, or the receiving function anymore.
addEvents: function(win) {
// The Window
var ow = win;
// Using this debug trick I can see that on the second time the events wont fire
// -- nothing gets printed to console
Ext.util.Observable.capture(ow, function(){
console.log(arguments);
});
// Will search for elements, finds elements that have class myclass
// In my case the elements are just ordinary html tags in the visible content
// area of the panel.
var elems = ow.down('panel').getEl().select(".myclass").elements;
Ext.Array.forEach(elems, function (item, index, allItems) {
// We need Ext DOM element to be able to attach stuff to it
var elem = new Ext.dom.Element(item);
elem.on ('click', function (evt, el, o) {
ow.fireEvent('myevent', ow, elem);
});
});
}
I suspected at first that I have to actually unregister the previous events and destroy the window, so I tried adding this to the close of the window:
window.down('panel').destroy();
window.destroy();
However it seems I have some other problem I really am unable to understand.
This is a very JQuery-ish way to add events. You are dealing with components and should add event listeners on components. If you need to delegate events down to html elements then you need to set a single event listener on the Component encapsulating the elements and add delegate config to the actual html elements.
Here are some resources:
Explain ExtJS 4 event handling
Event delegation explained:
http://www.sencha.com/blog/event-delegation-in-sencha-touch (applies to extjs just as well)
More on Listeners with extjs: https://stackoverflow.com/a/8733338/834424

Availability of UI elements in Marionette.View

I'd just like to understand the decisions behind Backbone.Marionette's view regarding UI elements.
When instantiating a Marionette.View on an existing DOM element, like this:
view = new Marionette.ItemView({
el: "#element",
ui : {
whatever : "#whatever"
}
});
I am able to access view.$el, the jquery selector inside view.initialize, so far so good.
However, when I try to access view.ui.whatever, I only have access to the selector, ie the string "#whatever" instead of the actual $("#whatever") jquery selector.
The reason for this is because Marionette.View.bindUIElements() is only called on render and not before initialize.
I would like to know if you think this behaviour is logic and why?
I am only asking in the case of attaching of the view to an existing el, if the view is created with a template, I do understand why the binding is in render().
Attaching a view to an existing element is the exception. The normal view lifecycle involves calling render, and without doing that there would be nothing for the UI elements to bind to.
Just call this.bindUIElements() in your initialize method when you need to attach a view to an existing element.
When I am working with Marionette, I put the code that has to access the ui elements inside the onShow method. This event is fired after the dom is ready and the elements are ready to be manipulated. Inside this method, your ui.whatever will now be pointing to an element and not a string.
I think you have that problem because you have to access to the jQuery element with
this.ui.whatever
Because "this" is already a view instance.
See: http://marionettejs.com/docs/v2.4.4/marionette.itemview.html#organizing-ui-elements

Debugging events that are not loaded in view

My events are not loading automatically. When I added delegateEvents() at the end of the render() method, it worked for a while. I do not want to use delegateEvents, but now, even with delegateEvents the events are not loading.
I reckon the DOM is not known at the time, so the events aren't bound, but how do I check (debug) that?
View:
class EditGroup extends BaseView
initialize: ->
#render()
render: ->
html = _.template tpl, #model.toJSON()
#$el.html html
for own key, options of FormConfig[#model.type]
options.key = key
options.value = #model.get key
input = new Input options
input.on 'valuechanged', (key, value) => #model.set key, value
#$('section.'+key).html input.$el
#delegateEvents() # doesn't work
#
DOM:
h2 Edit Group
section.title
section.type
section.members
button.save.btn.btn-primary(onclick="return false") Save changes
In the sections type and member there are typeaheads and selects rendered (Backbone views). One works without delegateEvents and the other works with. The events in the parent view (shown above) don't work at all. Removing the for-loop doesn't make any difference.
Ok, I solved it, but I don't understand the workings.
I'm using a 'view manager', which registers all views and shows (read: attaches the html to the DOM) the parent view. The view manager's show function is triggered when a new route is fired, but the route was fired double, once as "route:edit" and once as "route". I catch them with router.on "all", (eventName) -> etc.. I reckon the events are bound to the html, but the html is overridden by the second router event without the bindings and attached to the DOM. Question remains why the route is fired twice.

Dynamically created element used for Raphael canvas

I have created a Backbone.js/Require.js application that dynamically loads HTML templates to use as "pages" in the application. This means my main HTML page looks like so.
<head>
// Necessary CSS and Javascripts here
</head>
<body>
<div id="container"></div>
</body>
And then I used underscore templates to render new elements dynamically to the DOM. However, a new feature requires the use of a Raphael.js chart. I created a new element <div id='canvas'></div> and call Raphael('canvas') but since the canvas element wasn't there on DOM ready, Raphael can't see the newly created element.
I have attempted to use a jQuery selector in place of the id reference like so Raphael($('#canvas')) but this attaches the canvas to the body element and not my container element.
Any suggestions on how to bind a Raphael canvas to a dynamically created element?
Raphael($('#canvas').first(), '100%', '100%')
Though I had errors else where, the main issue that caused Raphael not to fire was forgetting that a jQuery selector passes an array of Elements and Raphael's constructor want's a single element. Raphael was attaching itself to the body because it was the top level parent of the selector's result.
Mosselman was also correct in pointing out that you can build a view in Backbone entirely in memory and then append it to the DOM.
A way to overcome this issue is by creating an empty element in the view and binding everything onto that. I have never worked with Raphael, but I think this could work:
var someView = Backbone.View.extend({
el: document.createElement('div'), // This creates a DOM element '<div></div>'
initialize: function(){
Raphael(this.el); // Attach Raphael, you could also go with jQuery
},
render: function(){
jQuery('#container').append(this.el); // Add to DOM somehow
}
})
seems like a good approach is to either throw an event after the template has been added to the DOM and have your call to Raphael('canvas') listen for that event or use a callback to trigger Raphael('canvas'). in both cases you are ensuring that you don't call Raphael('canvas') before the target element is in place.
very roughly, something like this:
//from your raphael module / code
$(document).on('canvasAdded', function(){
var paper = Raphael('canvas');
//stuff!
});
//after you are sure your template has rendered
$(document).trigger('canvasAdded');
you probably want to make some kind of .init() method and call that from the event handler (vs. what I show above) but hopefully this points you in the right direction.
I know that this is too old question, but anyway it can help to someone. Its important to be sure that your view is placed on page, so use something like onShow functionality, or render. But anyway Raphael will not show right because if your send to Raphael this.$el or anything similar it will not accept it like you expect. What You need to do is something like this.$el.first() or this.$el[0].

Backbone per instance event bindings

I have a view that creates a sub-view per item in the list. Generically let's call them ListView and ListItemView. I have attached an event as follows on ListItemView:
events: {
"click .remove": "removeItem"
}
I have template-generated html for ListItemView that is approximately like the following (swapped lb/rb for {/} so you can see the "illegal" html):
{div class="entry" data-id="this_list_item_id"}
SOME STUFF HERE
{div class="meta"}
{a class="remove" href="javascript:;"}[x]{/a}
{/div}
{/div}
The problem is, when the click on any of the [x]'s, ALL of the ListItemViews trigger their removeItem function. If I have it go off of this model's id, then I drop all the items on the page. If I have it go off the clicked item's parent's parent element to grab the data-id, I get a delete for EACH ListItemView instance. Is there a way to create an instance-specific event that would only trigger a single removeItem?
If I have ListView hold a single instance of ListItemView and reassign the ListItem model and render for each item in the list it works. I only end up with one action (removeItem) being triggered. The problem is, I have to find the click target's parent's parent to find the data-id attr. Personally, I think the below snippet is rather ugly and want a better way.
var that = $($(el.target).parent()).parent();
Any help anyone gives will be greatly appreciated.
It seems like your events hash is on your ListView.
If it is, then you can move the events hash to ListItemView and your removeItem function can be the following
removeItem: function() {
this.model.collection.remove(this.model);
}
If this isn't the case, can you provide your ListView and ListItemView code so I can look at it.
A wild guess but possible; check that your rendered html is valid. It might be possible that the dom is getting in a tiz due to malformed html

Resources