Integrating Fabricjs and Angularjs - angularjs

I'm trying to build an app which edits simple graphics on screen, fabricjs is the canvas library I use, and angularjs is the MVW framework I use.
Now, the bindings from DOM to fabric work just fine (I click a div, and corresponding object on canvas gets selected), but not the other way around. When I click an object on canvas, and it gets selected the corresponding DOM isn't updated. I've read here that I should be using $scope.$apply();, but I'm not sure where to put that.
How do I make fabric update $scope state?
You can see the code here, click the Add Rect button to add elements to the canvas, and notice that when you click the element's name on the right it get's selected on the canvas, but if you select it directly on the canvas it's button isn't high-lit.
code: http://plnkr.co/edit/lMogPGjJOXx9HLAdiYqB

FabricJS implements events on its classes, so one could bind arbitrary listeners to them. In your case, you could bind a listener to the "object:selected" event, which would call $scope.$apply() and would be fired whenever a object is selected on canvas.
See the Event Inspector Demo and the Observable Class Documentation. The Canvas class inherits all these methods, so you could bind listeners to it. You can even retrieve the selected object, as in the example:
var canvas = new Fabric.Canvas('canvas_container');
canvas.on("object:selected", function (options, event) {
var object = options.target; //This is the object selected
// You can do anything you want and then call...
$scope.$apply()
});

Based on the answer from Bernardo Domingues and MeLight's comment, it took me still some time to figure out.
The final solution for above is:
var canvas;
window.onload = function() {
canvas = new fabric.Canvas('canvas');
canvas.on("object:selected", function (options, event) {
//var object = options.target; //This is the object selected
var scope = angular.element(document.getElementById('canvas')).scope();
// You can do anything you want and then call...
scope.$apply();
});
};
The answer on AngularJS access scope from outside js function was very helpfull, together with http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
If you also want to see the position-information change when moving, add:
canvas.on("object:moving", function (options, event) {
var scope = angular.element(document.getElementById('canvas')).scope();
scope.$apply();
});

Related

Handling in View vs Handling in Controller Extjs

When we working on a extjs MVC project, we can handle a view event either inside the view or controller. How do we decide what events been handled in the view and what events been handled in the controller. What is the best practice.
For example, I need to pop up a window when a button is clicked. Should the creation of the window goes to controller or resides in the container view object.
One thing you can do is utilize the control() method within your controller definition. For example, take this code,
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'viewport > panel': {
render: this.onPanelRendered
},
'#editform button[action=save]': {
click: this.onClick
}
});
}
});
This is from the MVC example. You'll notice that it is using the ComponentQuery Language (http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.ComponentQuery) to select a set of Ext objects to assign the render() event. This is saying, for all panels in the viewport, call onPanelRendered() for the render() event. So in your case, you want to select some buttons. So I added another example that selects some form with an id of editform and then select the button that contains the property action = save. You can get very specific with these component queries. I would suggest reading ComponentQuery portion of the API to see further examples. This depends on how you are attempting to select the buttons.
If you are new to MVC maybe you should only listen to events in the controller, once you have developed a couple of projects and you've gained discipline and enjoyed the benefits of listening events in a controller then you would be able to mix it up (when to use a handler and when to use a controller is gonna become obvious).
E.g If you have a FormPanel and you are only gonna listen to a button's click perhaps it doesn't make much sense to create a controller just for that, but in the other hand if you have a grid with a toolbar, action buttons, itemClick's etc then it would be a good idea to have a controller for that view.
The fact that you are using MVC does not mean you are not allowed to use handlers anymore, but do it wisely.
Best regards.

Backbone delegateEvents not bubbling / propagating

Weird problem with event propagation in Backbone. Most people ask how to stop event propagation, but I'm struggling with getting my events to propagate!!
Here I have two View objects. The MainView which contains Item views and listens to click events to call run():
var MainView = Backbone.View.extend({
...
events: {
"click .item": "run" // works only if no click event in Item
},
render: {
// Item View object children
},
run: function() {
//run :)
}
});
Item view objects also listen to click events on themselves to toggle on/off behaviour:
var Item = Backbone.View.extend({
...
events: {
"click" : "toggle" // MainView click event works when this is removed
},
toggle: function() {
this.model.toggle();
}
});
The problem being that MainView.run() is not fired when the Item is clicked, while it has a click event for Item.toggle().
However, MainView.run() DOES fire if I remove the Item.toggle() click event. Leading me to the conclusion that the event is somehow forced to stop propagating, outside of my control.
How can I solve this problem? Am I missing something obvious, or is this unavoidable?
Thank you for any and all suggestions and answers :).
It appears that the click event in your item view isn't bound to a specific DOM object. It's possible that listening for a generic click event is overriding Backbone from listening for your specific .item click event. Try adding an ID or class name to your item view click event to remove any ambiguity.
var Item = Backbone.View.extend({
...
events: {
"click .some-class" : "toggle" // This should fix your problem
},
...
Jay B. Martin answered the question.
The problem is that the View calls this.model.toggle();
The toggle() function sets some variables which the MainView is listening for, causing a render() event to fire.
When MainView calls render(), the Item views are in turn removed, rendered and added to the DOM. This loses the bound event to the DOM element using events: {}.
Instead _.bind() or _.bindAll() should have been used to permanently bind the events, regardless of the context / state of the element bound to in the DOM.
Original comment answer:
#Dan0, sorry I'm a little confused about how toggle could be the root of your issue. I think it's a symptom of the context ambiguity created by binding to an implicit DOM element in a nested view. Once toggle is called, the click event loses the context to which it was initially bound (i.e., this.el). The idiomatic way of solving this is to either a) pass an explicit element so that it can rebind on subsequent events, or b) use _.bind or _.bindAll, so that the click event is permanently bound to the itemview as the context changes. – Jay B. Martin Aug 10 at 23:46

click event only working if button in certain location

I have a backbone application that gives the user the option to display a map by clicking a button. In the CharactersView, the 'showMap' event is set up (triggered by clicking the #map_button) with the corresponding showMap function to display a map once the event's triggered. The CharactersView also renders the character_template, and the character_template contains the #map_button and the actual map code with the class 'map.'Everything works fine if the #map_button is located in the same template (the chracters_template) as the map, which gets rendered from CharactersView. However, if I move the #map_button outside of the character_template (for example, into another template or directly into the body of the page), then the showMap event in CharactersView is no longer triggered, and the map no longer gets displayed in the character_template. Note, I am waiting until the characters template gets rendered to the page before I try to click the #map_button and it's still not working.
From the above description and the code below, can you tell me what the issue here might be and how I might solve it.
Code
Set up event binding in CharactersView so that a map is displayed when a button is clicked
events: {
'click #map_button' : 'showMap'
},
showMap: function(){
$(".map").toggle();
}
The character_template rendered by the characters view. The actual map with the class 'map' that gets displayed is in the same template that gets rendered by the characters view.
<script id="character_template" type="text/x-handlebars">
<input class='action_button' id='map_button' type='button' value='Show Map' />
<%= image_tag "http://maps.google.com/maps/api/staticmap?size=300x200&sensor=false&zoom=12&markers={{ latitude }}%2C{{ longitude }}", :class => "map" %>
</script>
If I removed the button from the character_template and put it in another tempalte, the click #map_button event in CharactersView no longer works. It's also the same result if I just put the button directly in the body of the html.
... other code ommitted...
When you set events in a view using the events hash, Backbone eventually uses jQuery to listen to the event. In Backbone.delegateEvents, you can see this code:
this.$el.on(eventName, selector, method);
Using this.$el.on() (where 'this' is your view) is only listening for the event within that view's DOM element ($el is a cached jQuery object).
So, if you move the button to another view, you need to add your event listener to that view as well:
events: {
'click #map_button' : 'showMap'
},
Basically, using the views event hash is saying, 'listen to this event only within this view's DOM element.'

Backbone.Marionette onRender callback fires before view is rendered in browser?

The setting
I have Backbone.Marionette.ItemView which renders some content. When the content is rendered I'd like to apply a jQuery plugin which turns part of the view into a container with a scrollbar.
The scrollbar is implemented completely in javascript and upon initialization it has to check the height of the scroll-container as well as that of the content inside the container.
If the content is higher then the container, a scrollbar should be enabled.
The problem
While this all sounds simple, I've ran into a strange problem:
If I initialize my scrollbar plugin directly in the onRender callback it seems to think the .scroll-container element has a height 0 and maxHeight of 0.
If I wrap the initialization code inside a 0ms timeout though, everything works as it should, the .scroll-container element's height property is returned by jQuery correctly and the scrollbar plugin works great.
The code
onRender: function() {
var that = this;
setTimeout(function() {
that.onLayout();
var $scrollContainer = that.$el.find('.scroll-container'),
scrollPane = new ScrollPane($scrollContainer, {
maxHeightProperty: 'maxHeight',
scrollUpButton: false,
scrollDownButton: false
});
}, 0);
},
The question
I'm assuming the problem occurs because the browser didn't finish actually rendering the newly inserted html when the onRender callback is executed.
Is this assumption correct? And if so, is my solution of using a 0ms timeout reliable under normal circumstances?
Since the plugin in question depends on the DOM, onRender won't do what you need. This callback kicks off after the view has been rendered, but there is no guarantee that the view's el has been added to the DOM yet. In fact, you are safe assuming the opposite - that it has not been added yet.
If you're using a Marionette Region to show the view, you can implement an onShow method in your view. This method gets called by the region, after the region has added the view to the DOM. It was implemented specifically to handle this situation.
For a little more info on this, and on working with jQuery plugins in general, see this blog post: http://lostechies.com/derickbailey/2012/02/20/using-jquery-plugins-and-ui-controls-with-backbone/

ExtJs ComboBox reinitializing

I'm trying to change ComboBox configuration by reinitializing:
Ext.onReady(function(){
var mycb = new Ext.form.ComboBox({
//params
});
//here is other component initizing
var other = ....
onChange: function() {
//here I'm trying to reinitialize ComboBox
mycb = new Ext.form.ComboBox({
// other params
});
}
});
But after onChange event my ComboBox's disapeared. I tried to invoke mycb.destroy() methods, but there's the same result.
Should I unregister or something like that ComboBox? Why my component is disapearing?
use below code ..
mycb.reset();
mycb.removeAll();
// for loading new data
mycb.loadData("new data store");
// to load attributes
mycb.load({params:{start:0, limit:25, reset:true}});
this is working in my code. Please change as per your need.
Probably a better idea would be to remove the original combobox from its container and add a new one in its place. Also perhaps all you need is to reload the store with new data?
Wrap this combo in panel with fit layout. In onChange handler remove combo from that panel, destroy it (combo), and add new combo to the panel. Having additional panel will give you easy way to put it in right place in the layout.

Resources