I have used jsTree plugin to render large number of tree node in my product.
Now I am in the process of moving to Ember, and need to implement jsTree plugin within Ember.
I wrote a Ember component to render my folder structure using jsTree.
My Component:
<script type="text/x-handlebars" data-template-name="components/temp-tree">
<div id="treediv">Tree Data</div>
</script>
App.TempTreeComponent = Ember.Component.extend({
didInsertElement: function(){
var self = this;
self.$().jstree({
'plugins':["contextmenu", "dnd"],
'core' : {
'data' : [
'Simple root node',
{
'text' : 'Root node 2',
'state' : {
'opened' : true,
'selected' : true
},
'children' : [
{'text' : 'Child 1'},
'Child 2'
]
}
],
'check_callback': true
}
})
.on('rename_node.jstree', function(e, data) {
alert('rename');
})
.on('delete_node.jstree', function(e, data) {
alert('delete');
});
},
actions: {}
});
JSBIN Demo
In my component for each action done on the tree, jsTree triggers an event respective to the event.
I used to listen to the events and do necessary action if required.
Basically jsTree updates the DOM and triggers the event.
But in Ember we will not update the DOM ,instead we need to update the underlying MODEL and by two way data-binding the DOM is updated by Ember.
Here I am going against the Ember Conventions.
Am I going in the right direction?
Is there any other way to use jsTree with Ember?
Or is there any jsTree like component available in Ember to render large number of tree nodes with all features like context menu, drag & drop, search, unique plugin, checkbox, lazy loading, updating nodes?
Answers to your questions.
Am I going in the right direction?. You can modularize your code better.
Is there any other way to use jsTree with Ember?. I don't know what you have in mind, but you have to wrap jQuery interface in something.
Is there any Ember extension like jsTree?. Take a look at ember-cli-jstree or ember-cli-tree.
Detailed response
We use Ember in our production app where we had to extend some jQuery plugins and I'll outline the way we did it.
There are three stages in the life cycle of a plugin, initialization with some options, user interactions triggering events and event handler manipulating states. The objective is to create a layer of abstraction over these stages following Ember conventions. The abstraction must not make the plugin documentation unusable.
App.PluginComponent = Em.Component.extend({
/***** initialization *****/
op1: null,
//default value
op2: true,
listOfAllOptions: ['op1', 'op2'],
setupOptions: function() {
//setup observers for options in `listOfAllOptions`
}.on('init'),
options: function() {
//get keys from `listOfAllOptions` and values from current object
//and translate them
//to the key value pairs as used to initialize the plugin
}.property(),
/***** event handler setup *****/
pluginEvents: ['show', 'hide', 'change'],
onShow: function() {
//callback for `show` event
},
setupEvents: function() {
//get event names from `pluginEvents`
//and setup callbacks
//we use `'on' + Em.String.capitalize(eventName)`
//as a convention for callback names
}.on('init'),
/***** initialization *****/
onHide: function() {
//change the data
//so that users of this component can add observers
//and computed properties on it
}
});
Related
The code below is from FullCalendar's Custom View documentation. It seems like a great start, but it would be very helpful for someone brand new like me to have some basic code that renders the most simple custom view (with some events). They tell you to look at BasicView and AgendaView as a reference, but it's a little beyond my understanding. Are each of the functions required to be overridden in the custom class?
This Plunker has a basic FullCalendar and a button to change to a custom view. What would be very helpful is to see a working example. I have been tinkering for hours with no success for a custom view. If you know FullCalendar and would be willing to fill in some code for the functions it would be very appreciated!
https://plnkr.co/edit/gfEUCVYTWTm1md24e33m?p=preview
My goal is to build a day list that lists all events of the day in order in a scrollable div (where each entry will eventually be quite fleshed out with data and css styling--I'm not sure if listDay would allow for this type of customization??).
var FC = $.fullCalendar; // a reference to FullCalendar's root namespace
var View = FC.View; // the class that all views must inherit from
var CustomView; // our subclass
CustomView = View.extend({ // make a subclass of View
initialize: function() {
// called once when the view is instantiated, when the user switches to the view.
// initialize member variables or do other setup tasks.
},
render: function() {
// responsible for displaying the skeleton of the view within the already-defined
// this.el, a jQuery element.
},
setHeight: function(height, isAuto) {
// responsible for adjusting the pixel-height of the view. if isAuto is true, the
// view may be its natural height, and `height` becomes merely a suggestion.
},
renderEvents: function(events) {
// reponsible for rendering the given Event Objects
},
destroyEvents: function() {
// responsible for undoing everything in renderEvents
},
renderSelection: function(range) {
// accepts a {start,end} object made of Moments, and must render the selection
},
destroySelection: function() {
// responsible for undoing everything in renderSelection
}
});
I've added a few lines to your plunker to make the custom view work. You can find here the example: https://plnkr.co/edit/8iOq15CsL2x6RPt29wgE?p=preview
Just to mention the changes:
In the calendar initializer the view definition has been added
$('#calendar').fullCalendar({
...
views: {
CustomView: {
type: 'custom',
buttonText: 'my Custom View',
click: $('#calendar').fullCalendar('changeView', 'CustomView')
}
}
});
In the custom view just added this in the render
$('.fc-view').append("<div>Insert your content here</div").css("background", "red");
In the custom view you get access to the events by doing this:
var myEvents=$('#calendar').fullCalendar('clientEvents');
From there on you can do your further customizations
I have a LayoutView that handles showing it's own children. According to the documentation, I can extend the childEvents object to automagically listen for any events triggered on it's child views.
This isn't working for me at all. Can anyone spot what I might be doing wrong?
Show.QuizLayout = Marionette.LayoutView.extend({
template:_.template("<h3><%= title %></h3><div id='quiz'></div>"),
regions: {
card: "#quiz",
},
childEvents: {
"next:question": "showNextQuestion",
},
showNextQuestion:function () {
//I NEVER GET CALLED!!!
},
onShow:function(){
var v = new Show.QuizCard();
this.showChildView('card',v)
});
Show.QuizCard = Marionette.ItemView.extend({
className: "quizcard",
template: _.template("<div class='card' id='next'>Next</div>"),
events: {
"click #next":function(e){
this.trigger("next:question")
}
},
});
I have gotten around this by setting v.on("next:question", function(){...}), but it creates cruft I'd rather not have to deal with, if I could get childEvents to work the way they're supposed to.
http://marionettejs.com/docs/v2.4.5/marionette.layoutview.html#layoutview-childevents
As noted in the docs, you should use triggerMethod within your childview, rather than trigger!
childEvents also catches custom events fired by a child view. Take
note that the first argument to a childEvents handler is the child
view itself. Caution: Events triggered on the child view through
this.trigger are not yet supported for LayoutView childEvents. Use
strictly triggerMethod within the child view.
Try:
childEvents: {
"next:question": this.showNextQuestion,
}
Or see if you have better luck with triggerMethod
events: {
"click #next":function(e){
this.triggerMethod("next:question")
}
}
Then in your layout have a method called onChildviewNextQuestion
I've hit a head-scratcher with a Backbone.js. The example is on jsfiddle here. I believe the issue is here:
App.Layout = new Backbone.Layout({
// Attach the Layout to the main container.
collection: App.chapters,
el: "body",
initialize: function () {},
beforeRender: function () {
// Add a sub-view for each Chapter
this.collection.each(function (model) {
this.insertView(model.get('id'), new App.ChapterView({
"id": model.get('id')
}));
}, this);
},
views: {
// But if I set the sub-view specifically if works
// "one": new App.ChapterView({id: 'one' })
}
});
In summary, the router should simply activate or deactivate backbone.layoutmanager sub-views based on the path, e.g., /#chapter/one, /#chapter/two, etc.
If I explicitly set the sub-views in App.Layout (see line 49 in the fiddle), the routing works as expected.
However, if I try to add the views by iterating a collection of models in the beforeRender function (line 40; beforeRender is coming from backbone.layoutmanager), they don't appear to be available when the router tries to find the matching view by ID.
Once the page has render, however, the view can be activated with:
App.router.navigate('/chapter/two',{"trigger": true});
Which seems to indicate that the views are properly being added and should be findable by the router with:
App.Layout.getView(name);
No doubt I'm simply overlooking something, or am about to expose my ignorance of the Backbone library. :)
The issue is that you're navigating and rendering out-of-sync. I've updated your code here: http://jsfiddle.net/6h268r7j/55/
It works when you use the declarative approach because those are outside of the render flow, essentially statically added. As soon as you use beforeRender/render you are now in an asynchronous render flow and they won't be available in your router callbacks.
The fix was to simply render the application layout first and then trigger the routing:
App.Layout.render().then(function() {
Backbone.history.start();
});
I'm working on a simple app which displays a list of issues of a particular repo on Github. Below is the code of IssueView which generates the html of an issue and insert to the DOM
App.IssueView = Ember.View.extend({
tagName: "li",
classNames: ["sugar", "issue_wrapper"],
templateName: "app/templates/issue",
init: function() {
App.LabelsController.addObserver("label", this, this.labelUpdated);
this._super();
},
click: function(event) {
var target = event.target;
if (target.className == "title") {
// Using bindingContext is a temporary solution to access data of this issue
App.IssuesController.set("issue", this.bindingContext);
App.IssuesController.set("state", "viewIssueDetails").notifyPropertyChange("state");
}
},
labelUpdated: function() {
this.labels = this.labels || this._collectLabels(),
label = App.LabelsController.get("label").name;
this.set("isVisible", this.labels.indexOf(label) != -1);
},
_collectLabels: function() {
var labels = [];
this.bindingContext.labels.forEach(function(label) { labels.push(label.name) });
return labels;
}
})
The way I generate it is
<script type="text/x-handlebars">
{{#view App.IssuesListView}}
{{#each App.IssuesController}}
{{view App.IssueView contentBinding="this"}}
{{/each}}
{{/view}}
</script>
The problem I had is with this line
App.LabelsController.addObserver("label", this, this.labelUpdated);
Everytime a new IssueView is generated and inserted into the DOM, I got an error "You cannot set observed properties on destroyed objects" when the 'label' property of LabelsController is updated. When I look into Firebug I saw that my IssueView's state is "destroy" instead of inDOM. I wonder why that happened and what can I do to get around it?
The #each helper in your template will ensure that IssueViews are created and destroyed as the collection of issues changes. You are manually adding the observer, which means you are responsible for removing the observer, too. I believe that using the observes(...) function prototype extension will handle that for you. (See http://ember-docs.herokuapp.com/symbols/Ember.Observable.html under "Observing Property Changes").
If you want to pursue the manual route, consider moving the addObserver to didInsertElement and adding a corresponding removeObserver in willDestroyElement.
One side note: if I'm understanding what you are trying to do with this code correctly, I would consider binding to an ArrayController that handles presenting the correct set of issues based on the selected label instead of the approach you are taking.
Today I have a small question regarding what the correct/best way is to add listeners using Sencha Touch 2 via it's recommended MVC model.
As far as I can see, there are two main ways that I have been presented with to add listeners to the various components across my views.
1. In the controller.
I came across this method while reading the MVC documents for ExtJS 4.0 (no MVC docs for touch yet). It goes like so:
init: function() {
console.log ('Launched the controller');
//listening test
//the Control function/method is unique to controllers and is used to add listeners to stuff
this.control({
'button': { 'tap' : function (){
console.log('the buttons speak!');
}
},
}
The above code would reside inside the main controller, for instance. Here, as you can see, I am adding a "tap" listener to ALL buttons across the entire app.
As far as I know, to access specific components in this way, I would need to tag them each with a unique ID and then use componentquery at this location to place listners onto them.
Question: I think this method is pretty cool... but I have run across problems using it in lists... sometimes I want to listen to a specific list item for things like "tapstart" and "tapend" but since usually listItems are dynamically created as children to a list... I have no idea how to give them unique IDs and/or find them using the query engine (due to my inexperience I guess? I haven't been able to google/find anything about it in the docs that makes sense).
2. During the init/config of individual components
The other method that I came across to add listeners to components is to define the listener, it's callback and the event it's listening to directly in the component config.
Example:
Ext.define('Paythread.view.CommentList', {
extend: 'Ext.Panel',
alias: 'widget.CommentList',
layout: 'vbox',
config : {
items: [
{
xtype: 'list',
layout: 'fit', //fullscreen: true,
height: 'viewport.height',
store: 'Comments',
onItemTap: function(){
//do stuff
},
pressedDelay: 20, //HOLY CRAP IMPORTANT FOR UX
itemTpl: '<h1>{user_id}</h1><h2>{comment}</h2>'
}
]
},
});
As you can see from this code, I have created a "onItemTap" listener function, and this seems to work pretty darn well. However... it scares me for some reason and I have no idea if what I am doing is correct or not.
Could anyone provide some help as to whether I am doing the right thing, if I should be doing this a different way, or if I am completely off track and shouldn't even be defining listeners like this in the first place?
I would really appreciate any help given! Thank you very much everyone.
The following method to add listeners looks a bit clearer to me:
Ext.define('Paythread.view.CommentList', {
extend: 'Ext.Panel',
alias: 'widget.CommentList',
layout: 'vbox',
config : {
items: [
{
xtype: 'list',
layout: 'fit', //fullscreen: true,
height: 'viewport.height',
store: 'Comments',
listeners: {
itemtap: function() {
//do stuff
}
}
pressedDelay: 20, //HOLY CRAP IMPORTANT FOR UX
itemTpl: '<h1>{user_id}</h1><h2>{comment}</h2>'
}
]
}
});
The tap function will be called when the list is tapped. AS simple as it sounds.
Hope it helps,
Chris
since you ask about listening to events on a list item, you should probably check out:
http://www.sencha.com/blog/event-delegation-in-sencha-touch
Unfortunately, the syntax is changed a bit with ST2. See this thread:
http://www.sencha.com/forum/showthread.php?154513-How-to-buffer-events-without-using-addListener
you have to add itemId propety in your list and by using this itemId you can get object of list and after that you can add listeners like this
init: function() {
console.log ('Launched the controller');
//listening test
//the Control function/method is unique to controllers and is used to add listeners to stuff
this.control({
Paythread.view.CommentList.query('itemIdof List')[0].on({
itemtap: function() {
alert('test')
}
});
},
}
this may help you
Thanks
There are several ways of adding listeners, and I think that there are a couple of considerations on each approach:
Should I put the listeners on the controller or on the view??
For me, that depends; if your view is basically a component that you are going to re-use, you probably want to tie up elements with events.
Although, you should not put the logic of the event on the view. Instead, your view should fire an event that can be listened by the controller.
Example:
Ext.define("App.view.SomePanel", {
extend: "Ext.form.Panel",
xtype: "somepanel",
config:{
scrollable:'vertical' ,
},
initialize: function () {
var backButton = {
xtype: "button",
ui: "back",
text: "Home",
handler: this.onBackButtonTap,
scope: this
};
this.add([
{
xtype: "toolbar",
docked: "top",
title: "Edit Player",
items: [ backButton ]
}
]);
},
onBackButtonTap: function () {
this.fireEvent("goBackCommand", this);
}
});
On this panel, we have a back button. The view shouldn't handle what the back button does (as it depends on the flow of the app rather than the view itself) but it should fire an event that we can catch on the controller.
Another very important thing that you should always take into consideration, is the scope of the the listener. Basically, the scope means what value this will have in the function triggered by the event. (In this example, the view itself)
Hope this helps!
Edit: link to Sencha guide about events:
http://docs.sencha.com/touch/2-0/#!/guide/events