Dynamically created element used for Raphael canvas - backbone.js

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].

Related

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

Marionette.js: Should regions replace instead of insert?

The default behavior of marionette.js is to append a template into the element specified by the regions selector. However, I usually end up having to create a special region type and override the appendHtml function to do a replace instead.
That is not too difficult, but why is append the default?
I usually will create the layout template with an empty div to specify where the region should go. Then I replace that div with sub template when I show it.
I guess am wondering if there I'm missing the design pattern for templates that makes append more intuitive.
Thanks for the help.
Update:
So I usually will have some view for something I want rendered into the page and will want to todd n element onto the page where I want it. I will do javascript that will look something like this:
ReplaceRegion = Marionette.Region.extend({
open: function(view){
this.$el.replaceWith(view.el);
}
});
App = new Backbone.Marionette.Application();
App.addRegions({
myRegion: {
selector: "#someRegion",
regionType: ReplaceRegion
}
};
var view = new CoolWidgetView();
App.myRegion.show(view);
And then somewhere in my html I'll throw an empty div in the mix where I want my template to show up.
<div id="mywidget"></div>
Now if it is the only child element, I can use a selector that would just be the parent, but that becomes more tricky when the view i'm inserting has siblings.
Also, I'm not really asking for a change to the default, as much as I'm wondering if there's a better way to insert items where you would like them in your layouts. I'm still relatively new to the layout and design of these things so anything helps!
Thanks Derick for writing great software!
Marionette regions provide both a show and a close method. Have you tried closing the region before you show the new one?
Marionette.Region docs

Re-Rendering Handlebars partial from backbone view

I have a view, which holds handlebars template.
that template consist of another partial template.
that partial template holds a list of results, which i am using in different parts of my app.
anyhow, when trying to filter the results, i'd like to render only that part. meaning the backbone view should not render the whole view just the partial.
can it be done?
Yes, it's possible. The easiest way is to execute the whole template as you do when rendering the complete view, but only replace the the part you need in the view's el.
Something like:
template: Handlebars.compile(templateHtml),
render: function() {
//let's say your render looks something like this
this.$el.html(this.template(this.model.toJSON());
},
renderList: function() {
var html = this.template(this.model.toJSON());
var selector = "#list";
//replace only the contents of the #list element
this.$el.find(selector).replaceWith($(selector, html));
}
Depending on how dynamic your template is, you may have to call this.delegateEvents() after replacing the list for the view's events to work correctly.
Edit based on comments:
To clarify, the method I propose here does execute the view's main handlebars template again, but it doesn't render the whole view again.
Step by step:
Execute the Handlebars template function as you do in normal render.
var html = this.template(this.model.toJSON());
The variable html now contains a string of HTML markup. Nothing has yet been rendered.
Define a selector for the element, which you would like to re-render.
var selector = "#list";
Find the DOM element to replace. This presumes that you have already rendered the view once. Otherwise there will be no #list element within this.$el.
this.$el.find(selector)
Find the corresponding element in the templated html string, and replace the existing element with the new one:
.replaceWith($(selector, html));
This will only replace the #list element that's currently on the page. Anything outside #list will not be re-rendered or touched in any way.
The main reason I propose you do it this way instead of executing and rendering the partial template separately is that your view doesn't need to know anything about the implementation details of the template and the templating engine. All it needs to know that there is an element #list. I believe this is a cleaner solution, and keeps your template details separate from your view logic.

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

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.

Correct way to insert a view with Backbone.js

I have a simple backbone.js app. I want to render a view into the DOM of the HTML page, this view is a detail view for a model. My HTML page already has the DIV element that I want to render the view into. If I try to render my view like this:
detailView = new RulesPanelView({model : #model})
$("#detail").html(detailView.render().el)
It fails and I get [Object HTMLDivElement] inserted into the DOM, not my rendered HTML.
This is the only way I can get it to work and it seems like a hack:
$("#detail").html('')
detailView = new RulesPanelView({model : #model})
$("#detail").append(detailView.render().el)
Having to empty the HTML of the DIV before rendering so I don't get multiple views rendered inside #detail which is what would happend with append.
Also aren't I creating way too many views this way, just seems cleaner to replace the HTML as in the first code segment?
What is the correct way to render this view?
What you want is to pass the already inserted DOM node to the view as a 'el' option to the constructor:
new RulesPanelView({el: $("#detail")});
This way, it won't render again. You still need to make sure your view's 'render' method will be able to render a correct view from an updated model, though.
The backbone documentation mentions this as a good way to avoid rendering too much stuff at once.
I actually append in the render method of the view. This doesn't work if you want to re-render when models change - but for that I've added a refresh method that render actually calls before appending. I then bind the refresh to the model change (if I need that). So in my View, I do this:
render: function(){
var markup = this.refresh();
$(markup).appendTo('#some-selector');
return this;
},
refresh: function(){
return $(this.el).html($.mustache(this.template, this.model.toJSON()));
},
Not sure if that's the "best", but I think it works pretty well. I've also seen where you have a collection bound to a view that loops through all of the models and renders "sub-views" of the collection view - this provides a nicer programmatic approach than hard-coding where you're going to append.

Resources