Using jsPlumb in an Ember.js Application - ember.js

I am trying to learn how to use jsPlumb in my Ember.js application so I put a minimal jsFiddle together to demonstrate how they could work together.
In this example so far I just insert the nodes and add them to jsPlumb. I have not added any links between them yet. At this stage the nodes should be draggable but they are not.
Error I get in the browser console:
TypeError: myOffset is null
Which points to this part of the code in jsPlumb:
for (var i = 0; i < inputs.length; i++) {
var _el = _getElementObject(inputs[i]), id = _getId(_el);
p.source = _el;
_updateOffset({ elId : id });
var e = _newEndpoint(p);
_addToList(endpointsByElement, id, e);
var myOffset = offsets[id], myWH = sizes[id];
var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e });
e.paint({ anchorLoc : anchorLoc });
results.push(e);
}
You can see that a simple example without integration with Ember.js works as expected. I know that this version of jsPlumb I have uses jquery-ui to clone elements and support drag and drop. A post here shows there is an issue with jquery-ui draggable functionality in Ember. However, I am not sure if I am hitting the same problem. If that is the same issue I am having, I would appreciate some help in how to implement the solution suggested there in my application. I am new to both Ember and jsPlumb, so I would appreciate clear guidance about what is going on here and what path to take.
How can I make this example work?

Luckily my suspicion was wrong and the issue was not with metamorph. jsPlumb and Ember work just fine together, without any hacks. I put a little example in this jsFiddle that demonstrates how they could work together.
Credit goes to Simon Porritt who helped me at jsPlumb user group to identify the problem. What I was missing was a simple call to jsPlumb.draggable element. However, the above error persisted after this fix.
The particular error message above was result of Ember calling didInsertElement an extra time with an element which did not make it to the DOM. I have reported this issue. One workaround is to check the element makes it into the DOM before calling jsPlumb. As you can see in the jsFiddle I have added this code in the didInsertElement hook to get rid of the error.
elementId = this.get 'elementId'
element = $("#"+elementId)
if element.size() > 0
console.log "added element", element
jsPlumb.addEndpoint element, endpoint
jsPlumb.draggable element
else
console.log "bad element"
Hope this helps someone.

Related

how to unit test sorting on a table from sapui5

I have this table (m.Table) in my ui5 application that I'm sorting using the following example.
https://sapui5.hana.ondemand.com/1.42.7/explored.html#/sample/sap.m.sample.TableViewSettingsDialog/preview
I have a set of unit test (QUnit) where I test this functionality but I keep getting an error saying that "Object doesn't support method sort".
This is a snippet of my code
var oBinding = controls.searchResultsTable.getBinding("items");
var aSorter = [];
var sPath = "columnName";
aSorter.push(new Sorter(sPath, false));//sort in descending order
oBinding.sort(aSorter);
This code above takes care of the sorting when the application runs and everything works fine.
Here is a snippet of my testing
QUnit.test("valid search input", function(assert){
....
//my attempt at testing this
var getBinding = sinon.stub().returns(new sap.ui.model.Binding(),
function(){});
}
var oBinding = {getBinding: getBinding};
....
The error points to the line where I do aBinding.sort(aSorter);
Looking at the api, the sap.ui.model.Binding doesn't have any "sort()" functions. So I'm not sure how the sorting even works, let alone test it. Could someone give me some guidance here?
because sort is done on sap.ui.model.ListBinding as you are doing a binding on table items. Link
Also there are different other Bindings which extends sap.ui.model.Binding like sap.ui.model.PropertyBinding(binding on single UI control), sap.ui.model.TreeBinding(binding on Tree control) and so on.

Ember active link

I have a route where I want to make a sibling route's link-to's active as well. I have tried using current-when in the link-to, but it's not working for me.
my routes are as follows
//projects
//projects/:project_id
//projects/:project_id/user/:user_id
When I navigate to //projects/:project_id route, the right link is set to active. I want the same link to be active on the //projects/:project_id/users/:user_id route.
My link-to in the parent //projects hbs template is
{{#link-to "projects.project" item.projectID current-when="projects.user" tagName="tr"}}
What am I doing wrong here?
UPDATE
I was able to get it to initially work when the route is rendered by using an edited version of #ykaragol's helper function and link-to...
{{#link-to "projects.project" item.projectName active=(calculate-active 'projects.user projects.project' item.projectName) tagName="tr"}}
compute(params, hash){
var pathname = window.location.pathname.split('/');
var pathProj = pathname[2];
var currRoute = this.get('currentRouteName');
var routes = params[0].split(' ');
if( ($.inArray( currRoute, routes) > -1) && (pathProj == params[1]) ){
return true;
}
return false;
}
But it's not updating when I click on a different project...
If routes don't have dynamic segments, it works as described in docs. I've tested it within this twiddle.
But I couldn't make it work while using dynamic segment. Please check this twiddle. I suspect this maybe a bug. You can ask this question in slack.
By the way, as a workaround, you can pass a boolean to the currentWhen property (as mentioned in docs). So you can write a helper or a computed property to calculate it with a regex.
Updated:
As a second workaround, you can handle active property of link-to by yourself. Try this twiddle.

Adding item to filtered result from ember-data

I have a DS.Store which uses the DS.RESTAdapter and a ChatMessage object defined as such:
App.ChatMessage = DS.Model.extend({
contents: DS.attr('string'),
roomId: DS.attr('string')
});
Note that a chat message exists in a room (not shown for simplicity), so in my chat messages controller (which extends Ember.ArrayController) I only want to load messages for the room the user is currently in:
loadMessages: function(){
var room_id = App.getPath("current_room.id");
this.set("content", App.store.find(App.ChatMessage, {room_id: room_id});
}
This sets the content to a DS.AdapterPopulatedModelArray and my view happily displays all the returned chat messages in an {{#each}} block.
Now it comes to adding a new message, I have the following in the same controller:
postMessage: function(contents) {
var room_id = App.getPath("current_room.id");
App.store.createRecord(App.ChatMessage, {
contents: contents,
room_id: room_id
});
App.store.commit();
}
This initiates an ajax request to save the message on the server, all good so far, but it doesn't update the view. This pretty much makes sense as it's a filtered result and if I remove the room_id filter on App.store.find then it updates as expected.
Trying this.pushObject(message) with the message record returned from App.store.createRecord raises an error.
How do I manually add the item to the results? There doesn't seem to be a way as far as I can tell as both DS.AdapterPopulatedModelArray and DS.FilteredModelArray are immutable.
so couple of thoughts:
(reference: https://github.com/emberjs/data/issues/190)
how to listen for new records in the datastore
a normal Model.find()/findQuery() will return you an AdapterPopulatedModelArray, but that array will stand on its own... it wont know that anything new has been loaded into the database
a Model.find() with no params (or store.findAll()) will return you ALL records a FilteredModelArray, and ember-data will "register" it into a list, and any new records loaded into the database will be added to this array.
calling Model.filter(func) will give you back a FilteredModelArray, which is also registered with the store... and any new records in the store will cause ember-data to "updateModelArrays", meaning it will call your filter function with the new record, and if you return true, then it will stick it into your existing array.
SO WHAT I ENDED UP DOING: was immediately after creating the store, I call store.findAll(), which gives me back an array of all models for a type... and I attach that to the store... then anywhere else in the code, I can addArrayObservers to those lists.. something like:
App.MyModel = DS.Model.extend()
App.store = DS.Store.create()
App.store.allMyModels = App.store.findAll(App.MyModel)
//some other place in the app... a list controller perhaps
App.store.allMyModels.addArrayObserver({
arrayWillChange: function(arr, start, removeCount, addCount) {}
arrayDidChange: function(arr, start, removeCount, addCount) {}
})
how to push a model into one of those "immutable" arrays:
First to note: all Ember-Data Model instances (records) have a clientId property... which is a unique integer that identifies the model in the datastore cache whether or not it has a real server-id yet (example: right after doing a Model.createRecord).
so the AdapterPopulatedModelArray itself has a "content" property... which is an array of these clientId's... and when you iterate over the AdapterPopulatedModelArray, the iterator loops over these clientId's and hands you back the full model instances (records) that map to each clientId.
SO WHAT I HAVE DONE
(this doesn't mean it's "right"!) is to watch those findAll arrays, and push new clientId's into the content property of the AdapterPopulatedModelArray... SOMETHING LIKE:
arrayDidChange:function(arr, start, removeCount, addCount){
if (addCount == 0) {return;} //only care about adds right now... not removes...
arr.slice(start, start+addCount).forEach(function(item) {
//push clientId of this item into AdapterPopulatedModelArray content list
self.getPath('list.content').pushObject(item.get('clientId'));
});
}
what I can say is: "its working for me" :) will it break on the next ember-data update? totally possible
For those still struggling with this, you can get yourself a dynamic DS.FilteredArray instead of a static DS.AdapterPopulatedRecordArray by using the store.filter method. It takes 3 parameters: type, query and finally a filter callback.
loadMessages: function() {
var self = this,
room_id = App.getPath('current_room.id');
this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
})
// set content only after promise has resolved
.then(function (messages) {
self.set('content', messages);
});
}
You could also do this in the model hook without the extra clutter, because the model hook will accept a promise directly:
model: function() {
var self = this,
room_id = App.getPath("current_room.id");
return this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
});
}
My reading of the source (DS.Store.find) shows that what you'd actually be receiving in this instance is an AdapterPopulatedModelArray. A FilteredModelArray would auto-update as you create records. There are passing tests for this behaviour.
As of ember.data 1.13 store.filter was marked for removal, see the following ember blog post.
The feature was made available as a mixin. The GitHub page contains the following note
We recommend that you refactor away from using this addon. Below is a short guide for the three filter use scenarios and how to best refactor each.
Why? Simply put, it's far more performant (and not a memory leak) for you to manage filtering yourself via a specialized computed property tailored specifically for your needs

Computed property being observed doesn't fire if changed twice in a row

I have an Ember.Object that I'm updating with a property like below, but if I change primaryDemo twice in a row, it doesn't fire, yet if I change primaryDemo, then Rate, it does change. I'm puzzled as to why this is and how I can fix it.
dependantChanged: function() {
console.log('Firing change');
this.get('_update')(this);
}.observes('primaryDemo', 'Rate', 'Totals'),
UPDATE: So the first answer and fiddle got me thinking as to what the problem was, and it's due to changing a property on an object and not the object itself. I think ember does a hash check to see if there is a difference. In my case I'm already using underscorejs, so I just change the property, then use _.clone(demo) before doing the set. I'd rather not do that, so will wait to see if there is a more elegant solution before closing this.
You don't need to set primaryDemo again. In the example that does nothing. You need to force tell Ember to notify your observer. See this fiddle...
var demo = { Imps: 1, Demo: { Id: 2 } }
var obj = Ember.Object.create({
dependantChanged: function() {
console.log('Firing change');
}.observes('primaryDemo', 'Rate', 'Totals'),
});
obj.set('primaryDemo', demo);
demo.Imps = 2;
obj.set('primaryDemo', demo);
// Notify observers on obj#primaryDemo
Ember.notifyObservers(obj, 'primaryDemo');
​
Can you give more details? I created a simple JSFiddle http://jsfiddle.net/JjbXb/ from your description but changing the same property in a row, as you say, works.
Are you sure the value of primaryDemo is different in your 2 consecutive calls?

Proper design of REST-powered list in Ember.js

I'm having difficulty wrapping my head around the following:
There's a view that displays the list of items
I take the list of items from the backend via RESTful interface in JSON using ember-data and hand-crafted adapter
In my view I do something like this:
{{#collection contentBinding="App.recentAdditionsController"}}
...
{{/collection}}
App.recentAdditionsController is defined like this:
App.recentAdditionsController = Em.ArrayController.create({
refresh: function(query) {
var items = App.store.findAll(App.Item);
this.set('content', items);
}
});
And... this doesn't work. The reason being App.store.findAll() returning ModelArray which is much like ArrayController itself.
I saw people doing something like this:
App.recentAdditions = App.store.findAll(App.Item);
I could imagine doing it like that, but how would I refresh the list at will (checking if there's anything new).
Hope all is clear more or less.
I've verified that you can use a ModelArray inside an ArrayController. Here's a jsFiddle example: http://jsfiddle.net/ebryn/VkKX2/
"Now the question is how to make the list update itself if there are new objects in the backend?"
Use App.Model.filter to keep your recordArray in sync. Add the query hash when the filter is invoked to ensure than an initial query was made.
model: ->
App.Model.filter {page: 1}, (data) ->
data
edit: Just saw how old the question was, but leaving it here in case it helps someone.