I'm trying to implement drag and drop with Ember.js. I have everything working except getting the view to update at the end. Here's a jsfiddle:
http://jsfiddle.net/rsaNy/
If you drag something from the source list and drop it in the dest div, I want it to be added to the display. The two alerts that I have set up confirm that the data structures are actually being updated, but the update is not being reflected in the view. How do I solve this? Here's the code.
DragNDrop = Ember.Namespace.create();
DragNDrop.cancel = function(event) {
event.preventDefault();
return false;
};
App = Ember.Application.create();
testItems =
[ { name: "Alpha" } ,
{ name: "Bravo" } ,
{ name: "Charlie" } ];
App.SourceItemView = Ember.View.extend({
templateName: 'sourceItem',
tagName: 'li',
attributeBindings: 'draggable',
draggable: 'true',
dragStart: function(event) {
var dataTransfer = event.originalEvent.dataTransfer;
var msg = this.get('content');
dataTransfer.setData('text', JSON.stringify(msg));
}
});
App.SourceView = Ember.View.extend({
templateName: 'source',
tagName: 'div',
classNames: ['well']
});
App.DestView = Ember.View.extend({
templateName: 'dest',
tagName: 'div',
classNames: ['well'],
dragEnter: DragNDrop.cancel,
dragLeave: DragNDrop.cancel,
dragOver: DragNDrop.cancel,
drop: function(event) {
var c = this.get('content');
var fieldStr = event.originalEvent.dataTransfer.getData('text');
alert(fieldStr);
c.push(JSON.parse(fieldStr));
var str = '';
for (var i = 0; i < c.length; i++) {
str += c[i].name+' ';
}
alert(str);
return true;
}
});
App.IndexController = Ember.ObjectController.extend({
content: { source: testItems,
dest: [{name: "Delta"}] }
});
You need to use the Ember Array pushObject() method instead of push(). This allows Ember to keep track of the data changes for it's binding system, which is how the view knows to update itself. It is the same idea of having to use get() and set() all the time.
drop: function(event) {
var c = this.get('content');
var fieldStr = event.originalEvent.dataTransfer.getData('text');
alert(fieldStr);
c.pushObject(JSON.parse(fieldStr));
//...
Here is the working JSFiddle
Related
The problem with this code is that the render code is entered twice, and the buffer is not where I expect it. Even when I get the buffer, the stuff I push in is not rendered to the screen.
App.FilterView = Ember.View.extend({
init: function() {
var filter = this.get('filter');
this.set('content', App.ViewFilter.find(filter));
this._super();
},
render: function(buffer) {
var content = this.get('content');
if(!this.get('content.isLoaded')) { return; }
var keys = Object.keys(content.data);
keys.forEach(function(item) {
this.renderItem(buffer,content.data[item], item);
}, this);
}.observes('content.isLoaded'),
renderItem: function(buffer, item, key) {
buffer.push('<label for="' + key + '"> ' + item + '</label>');
}
});
And the App.ViewFilter.find()
App.ViewFilter = Ember.Object.extend();
App.ViewFilter.reopenClass({
find: function(o) {
var result = Ember.Object.create({
isLoaded: false,
data: ''
});
$.getJSON("http://localhost:3000/filter/" + o, function(response) {
result.set('data', response);
result.set('isLoaded', true);
});
return result;
}
});
I am getting the data I expect and once isLoaded triggers, everything runs, I am just not getting the HTML in my browser.
As it turns out the answer was close to what I had with using jquery then() on the $getJSON call. If you are new to promises, the documentation is not entirely straight forward. Here is what you need to know. You have to create an object outside the promise - that you will return immediately at the end and inside the promise you will have a function that updates that object once the data is returned. Like this:
App.Filter = Ember.Object.extend();
App.Filter.reopenClass({
find: function(o) {
var result = Ember.Object.create({
isLoaded: false,
data: Ember.Object.create()
});
$.getJSON("http://localhost:3000/filter/" + o).then(function(response) {
var controls = Em.A();
var keys = Ember.keys(response);
keys.forEach(function(key) {
controls.pushObject(App.FilterControl.create({
id: key,
label: response[key].label,
op: response[key].op,
content: response[key].content
})
);
});
result.set('data', controls);
result.set('isLoaded', true);
});
return result;
}
});
Whatever the function inside then(), is the callback routine that will be called once the data is returned. It needs to reference the object you created outside the $getJSON call and returned immediately. Then this works inside the view:
didInsertElement: function() {
if (this.get('content.isLoaded')) {
var model = this.get('content.data');
this.createFormView(model);
}
}.observes('content.isLoaded'),
createFormView: function(data) {
var self = this;
var filterController = App.FilterController.create({ model: data});
var filterView = Ember.View.create({
elementId: 'row-filter',
controller: filterController,
templateName: 'filter-form'
});
self.pushObject(filterView);
},
You can see a full app (and bit more complete/complicated) example here
I'm trying add a delete button with an ember action from a controller. For some reason Ember.Handlebars.compile('<button {{action "deletePerson"}}>Delete</button> returns a function and not the compiled string.
Here's a jsbin
Here's the relevant portion of code:
App.ApplicationController = Ember.Controller.extend({
columns: function() {
...
buttonColumn = Ember.Table.ColumnDefinition.create({
columnWidth: 100,
headerCellName: 'Action',
getCellContent: function(row) {
var button = Ember.Handlebars.compile('<button {{action "deletePerson" this}}>Delete</button>');
return button; // returns 'function (context, options) { ...'
}
});
...
}.property()
...
After looking through the link from #fanta (http://addepar.github.io/#/ember-table/editable) and a lot of trial and error, I got it working.
Here's the working jsbin.
Here are some key points:
Instead of using getCellContent or contentPath in the ColumnDefinition, you need to use tableCellViewClass and to create a view that will handle your cell
Pass in this to the action on your button — and modify content off that. One gotcha is to edit content, you need to copy it using Ember.copy
Here's the relevant code:
App.ApplicationController = Ember.Controller.extend({
columns: function() {
...
buttonColumn = Ember.Table.ColumnDefinition.create({
columnWidth: 100,
headerCellName: 'Action',
tableCellViewClass: 'App.PersonActionCell'
});
...
}.property(),
onContentDidChange: function(){
alert('content changed!');
}.observes('content.#each'),
...
});
App.PersonActionCell = Ember.Table.TableCell.extend({
template: Ember.Handlebars.compile('<button {{action "deletePerson" this target="view"}}>Delete</button>'),
actions: {
deletePerson: function(controller){
// Will NOT work without Ember.copy
var people = Ember.copy(controller.get('content'));
var row = this.get('row');
// For some reason people.indexOf(row) always returned -1
var idx = row.get('target').indexOf(row);
people.splice(idx, 1);
controller.set('content', people);
}
}
});
I'm a Backbone noob and I've been at a standstill for 2 days now and can't figure out where I'm going wrong. Could anyone help me out?
My app is retrieving a JSON file with a list of components in it. Each component has a category it belongs to. I create a view called "Components" that is a collapsible list. When a component category is clicked, it should open up to show the components in that category. Each of these components (list items) a separate view called "Component".
I'm using a lot of append()'s in the parent view and I don't think this is efficient. I tried to compile a string of html and then append it to the view in one statement but the events of the subviews weren't triggering.
There are probably a few errors going on here. Even though my sublist items should be wrapped in ul's they aren't being. If someone can put me on the path to enlightenment I'd be really grateful!
Here's my code
/* ----------------- PARENT VIEW ---------------------- */
var ComponentsView = Backbone.View.extend({
id: 'components-view',
className: 'components-view',
html: [
'<div class="panel panel--components">',
'<h3 class="panel__heading">add an item</h3>',
'<ul class="component-list"></ul>',
'</div>'
].join(''),
initialize: function(){
var types = [];
var currentTypeSelected = 1;
this.getTypes = function(){
return types;
}
this.getCurrentTypeSelected = function(){
return currentTypeSelected;
}
this.setCurrentTypeSelected = function(value){
currentTypeSelected = value;
}
if(this.collection.length){
this.collection.each(function(model){
var thisItemType = model.attributes.type;
if(types.indexOf(thisItemType)==-1){
types.push(thisItemType);
}
});
}
this.$el.html(this.html);
this.$componentList = this.$('.component-list');
this.render();
},
render: function(){
var that = this;
this.getTypes().forEach(function(type){
that.$('.component-list').append('<li class="component-type">' + type + '');
// now cycle through all the componenets of this type
that.$('.component-list').append('<ul>');
that.collection.byType(type).each(function(model){
that.$('.component-list').append('<li class="component">');
that.$('.component-list').append(that.renderIndividualComponent(model));
that.$('.component-list').append('</li>');
});
that.$('.component-list').append('</ul>');
});
},
renderIndividualComponent: function(model){
var componentView = new ComponentView({model: model});
return componentView.$el;
},
events: {
'click .component-type': 'onOpenSubList'
},
onOpenSubList: function (e) {
alert('open sub list');
}
});
/* ----------------- SUB (list item) VIEW ---------------------- */
var ComponentView = Backbone.View.extend({
tagName: "li",
className: "component",
initialize: function(model){
this.render();
},
render: function(){
var html = '' + this.model.attributes.description + ''//template(this.model.attributes);
$(this.el).append(html);
return this;
},
events: {
'click a': 'onAddComponent'
},
onAddComponent: function (e) {
e.preventDefault();
alert('add component');
}
});
I have the following controller:
var ProductsController = Ember.ArrayController.extend({
search: function(queryString) {
this.set('model', App.Product.find({query: queryString }));
}
});
and a text field:
var SearchFieldView = Ember.TextField.extend({
insertNewline: function() {
this.get('controller').search(this.get('value'));
}
});
Now I want to disable the text field when the controller loads a new model in the search function. Using something like disabledBinding: 'controller.content.isLoaded' in the view doesn't work.
var ProductsController = Ember.ArrayController.extend({
search: function(queryString) {
this.set('isLoadingData', true);
var products = App.Product.find({query: queryString });
this.set('model', products);
products.then(function() {
this.set('isLoadingData', false);
});
}
});
var SearchFieldView = Ember.TextField.extend({
attributeBindings: ['disabled'],
disabledBinding: 'controller.isLoadingData',
insertNewline: function() {
this.get('controller').search(this.get('value'));
}
});
Explanation:
Before doing a request set isLoadingData to true. ember-data find() uses the Promise API: Set the isLoadingData to false when the request has been completed successfully. You might want to handle the failed case. See RSVP.js for reference. Finally bind the disabled property of Ember.TextField to controller.isLoadingData.
A simpler way, as you have already tried:
var ProductsController = Ember.ArrayController.extend({
search: function(queryString) {
this.set('model', App.Product.find({query: queryString }));
}
});
var SearchFieldView = Ember.TextField.extend({
attributeBindings: ['disabled'],
disabled: function() {
return this.get('controller.model.isLoaded') === false;
}.property('controller.model.isLoaded'),
insertNewline: function() {
this.get('controller').search(this.get('value'));
}
});
If you want all the Ember.TextField to have the disabled property binding:
Ember.TextField.reopen({
attributeBindings: ['disabled']
});
How do i change an elements class on click via ember.js, AKA:
<div class="row" {{bindAttr class="isEnabled:enabled:disabled"}}>
View:
SearchDropdown.SearchResultV = Ember.View.extend(Ember.Metamorph, {
isEnabled: false,
click: function(){
window.alert(true);
this.isEnabled = true;
}
});
The click event works as window alert happens, I just cant get the binding to.
The class is bound correctly, but the isEnabled property should be modified only with a .set call such as this.set('isEnabled', true) and accessed only with this.get('isEnabled'). This is an Ember convention in support of first-class bindings and computed properties.
In your view you will bind to a className. I have the following view in my app:
EurekaJ.TabItemView = Ember.View.extend(Ember.TargetActionSupport, {
content: null,
tagName: 'li',
classNameBindings: "isSelected",
isSelected: function() {
return this.get('controller').get('selectedTab').get('tabId') == this.get('tab').get('tabId');
}.property('controller.selectedTab'),
click: function() {
this.get('controller').set('selectedTab', this.get('tab'));
if (this.get('tab').get('tabState')) {
EurekaJ.router.transitionTo(this.get('tab').get('tabState'));
}
},
template: Ember.Handlebars.compile('<div class="featureTabTop"></div>{{tab.tabName}}')
});
Here, you have bound your className to whatever the "isSelected" property returns. This is only true if the views' controller's selected tab ID is the same as this views' tab ID.
The code will append a CSS class name of "is-selected" when the view is selected.
If you want to see the code in context, the code is on GitHub: https://github.com/joachimhs/EurekaJ/blob/netty-ember/EurekaJ.View/src/main/webapp/js/app/views.js#L100
Good answers, however I went down a different route:
SearchDropdown.SearchResultV = Ember.View.extend(Ember.Metamorph, {
classNameBindings: ['isSelected'],
click: function(){
var content = this.get('content');
SearchDropdown.SelectedSearchController.set('content', content);
var loadcontent = this.get('content');
loadcontent.set("searchRadius", $("select[name=radius]").val());
SearchDropdown.LoadMap.load(content);
},
isSelected: function () {
var selectedItem = SearchDropdown.SelectedSearchController.get('content'),
content = this.get('content');
if (content === selectedItem) {
return true;
}
}.property('SearchDropdown.SelectedSearchController.content')
});
Controller:
SearchDropdown.SelectedSearchController = Ember.Object.create({
content: null,
});
Basically stores the data of the selected view in a controller,