Cytoscape.js: hide cxtmenu entries depending on node? - contextmenu

I use cytoscape-cxtmenu.js to provide commands on nodes but not all nodes support all commands. How can I only show the commands that a node supports?
var defaults = {
selector: 'node',
commands: [
// everyone has a name
{content: 'tell name',
select: function(node) {console.log(node.data().name);}},
// not everyone has a spouse
{content: 'tell spouse',
select: function(node) {console.log(node.data().spouse);}},
]};
var cxtmenuApi = cy.cxtmenu(defaults);

Use multiple context menus, each with a different selector.
var cxtmenuApi1 = cy.cxtmenu({ selector: 'node[foo="bar"]' /* ... */ });
var cxtmenuApi2 = cy.cxtmenu({ selector: 'node[foo="baz"]' /* ... */ });
Each menu has its own set of commands, so you can just use the same commands but with some commands disabled (disabled: true) in 1 but enabled in 2, etc.
Alternatively, you could specify only the commands that apply to each set of nodes -- but then the relative positions of commands could be different from node to node.

Related

How to create a space in Tarantool?

I've started Tarantool and have called box.cfg{} for the first configuring.
The next step: I want to create a space in Tarantool.
I read the documentation but I didn't quite understand everything.
How I should do it?
You don't need to create the sequence manually; just pass true and tarantool will create a sequence and even delete it when you drop the space. You can also skip the parts option as it defaults to {1, 'unsigned'}
box.space.users:create_index("pk", { if_not_exists = true, sequence = true })
Create it via Box API:
box.schema.sequence.create('user_seq', { if_not_exists = true })
box.schema.create_space('users', { if_not_exists = true, format={
{ name = 'id', type = 'unsigned'},
{ name = 'name', type = 'string'},
{ name = 'age', type = 'unsigned'}}
})
box.space.users:create_index('pk', { parts = { 'id' }, if_not_exists = true })
With if_not_exists, tarantool won't try to create space if it already exists.
Creating the index is mandatory because Tarantool doesn't allow you to insert data in space without any indexes.
After creating the space, you can insert and select data:
box.space.users:insert({ box.sequence.user_seq:next(), 'Artur Barsegyan', 24 })
box.space.users:get({1})
---
- - [1, 'Artur Barsegyan']
...
You can read more in the documentation.

How can we copy labels from one node to another in one cypher?

The question is just as the title, and the cypher statement is just like the follow:
Match (n: test) CREATE (copy : LABELS(n)) set copy = n
Its purpose is to create a node with the same properties and same labels as the other node, but it doesn't work now because we cannot use a expression like LABELS(n) to set lobel to a node.
How can I make it work?
Unfortunately, labels currently cannot be set directly from data values.
You could get the node's properties and labels you want to copy and then dynamically create another cypher statement that you execute.
Using the transactional api, it could look like this:
// requires cypher-rest
// npm i cypher-rest
'use strict';
const util = require('util');
const c = require('cypher-rest');
const neoUrl = 'http://127.0.0.1:7474/db/data/transaction/commit';
const copyNode = propertyObjMatch => {
return new Promise((resolve, reject) => {
// find node(s) via property matching and return it(/them)
const cypher = `MATCH (x ${util.inspect(propertyObjMatch)}) RETURN DISTINCT x, LABELS(x) AS labels`;
return c.run(cypher, neoUrl, true) // third parameter set to true to always return a list of results
.then(results => {
// iterate over results and create a copy one by one
results.forEach(result => {
const copy = `CREATE (copy:${[...result.labels].join(':')}) SET copy = ${util.inspect(result.x)} RETURN copy`;
c.run(copy, neoUrl);
});
})
});
};
// create a node
c.run('CREATE (x:LABEL1:LABEL2 {withProp: "and value", anotherProp: "test"}) RETURN x', neoUrl).then(() => {
copyNode({withProp: 'and value', anotherProp: 'test'})
.then(console.log)
});
Please excuse the hackiness, but it should bring the point across.

array observable with content observable and jqAutocomplete

I'm using Knockout 3 with the plugin jqAutocomplete by Ryan Niemeyer. I have a problem with this model:
var ViewModel = function() {
var self = this;
self.myOptionsObs = ko.observableArray([
{ id: ko.observable(1), name: ko.observable("item 1 o"), description: ko.observable("item label 1 o") },
{ id: ko.observable(2), name: ko.observable("item 2 o"), description: ko.observable("item label 2 o") },
{ id: ko.observable(3), name: ko.observable("item 3 o"), description: ko.observable("item label 3 o") }
]);
self.myValueObs = ko.observable();
};
ko.applyBindings(new ViewModel());
<input data-bind="jqAuto: { source: myOptionsObs, value: myValueObs, inputProp: 'name', template: 'itemTmpl' }" />
As you can see, there is an observable array and each element is also an observable.
The autocomplete don't work well. As you can see in this Fiddle, the left column has an observable array but its elements aren't observable. If you click in the left box and write something, a list of options appear.
But in the right column, you have the same, but the element's are all observable. If you click in the right box and write something, when the list appear, if you move the cursor up and down, you could see that the row 'name' gets deleted and filled with zeros.
What I have to change in my data-bind attribute?
This question is related with this question.
I have to say that this solution works ok for me. But the updated plugin don't.
Thanks !!
The jqAutoComplete plugin isn't setup to work with observable properties (although it could be enhanced to do so without much work).
For now, I think that your best bet is to create a computed that will always return a plain and up-to-date version of your options.
self.myOptionsObs.plain = ko.computed(function() {
return ko.toJS(self.myOptionsObs);
});
Sample: http://jsfiddle.net/rniemeyer/45cepL9b/
I'll try to take a look at some point about supporting observable properties. Shouldn't take many changes.

Neo4j: Match three or more relationships to a single dynamic node

I am trying out Neo4j for a personal project to implement a recommendation system. The system takes several strings as Input and Outputs a recommendation. The system has nodes in the form of Animals and Groups. The relationship between animals and groups is that an animal belongs to a group. An animal can belong to multiple groups.
The input can be any number of animals. The question I am attempting to answer is "Which animals are present in the groups that contain all the input animals?"
An example of correct output:
Input: Lion, Parrot, Giraffe
Output: Elephant, Zebra
The lion, parrot and giraffe all belong to group 2 and 3. The elephant belongs to group 2 and the zebra belongs to group 3, so they are outputted.
My current solution:
Match (:Animal { name: "Parrot" })
-[:BELONGS_TO]->(matchingGroup:Group)
<-[:BELONGS_TO]-(:Animal { name: "Lion" }),
(:Animal { name: "Giraffe" })
-[:BELONGS_TO]->matchingGroup
<-[:BELONGS_TO]-(animalsInMatchingGroup:Animal)
Return animalsInMatchingGroup.name AS name, count(animalsInMatchingGroup.name) as matches
ORDER BY count(animalsInMatchingGroup.name) DESC
The Problem:
The problem arises when I have more than two animals in my query. In the query above I am querying the graph using Match statements equal to the number of input animals - 1. I am wondering if anyone knows of a better solution to this problem that will prevent querying the graph multiple times.
This is the graph.
http://s29.postimg.org/inhhvqcd3/Screen_Shot_2014_10_05_at_8_09_23_PM.png
Create statement
CREATE
(elephant:Animal { name: 'Elephant' }),
(lion:Animal { name: 'Lion' }),
(tiger:Animal { name: 'Tiger' }),
(giraffe:Animal { name: 'Giraffe' }),
(parrot:Animal { name: 'Parrot' }),
(zebra:Animal { name: 'Zebra' }),
(group1:Group { name: 'Group 1' }),
(group2:Group { name: 'Group 2' }),
(group3:Group { name: 'Group 3' }),
(group4:Group { name: 'Group 4' }),
(group5:Group { name: 'Group 5' }),
elephant-[:BELONGS_TO]->group2,
elephant-[:BELONGS_TO]->group3,
lion-[:BELONGS_TO]->group1,
lion-[:BELONGS_TO]->group2,
lion-[:BELONGS_TO]->group3,
parrot-[:BELONGS_TO]->group2,
parrot-[:BELONGS_TO]->group3,
parrot-[:BELONGS_TO]->group5,
giraffe-[:BELONGS_TO]->group2,
giraffe-[:BELONGS_TO]->group3,
giraffe-[:BELONGS_TO]->group4,
tiger-[:BELONGS_TO]->group5,
zebra-[:BELONGS_TO]->group4,
zebra-[:BELONGS_TO]->group3
Thanks for your help.
Cheers, Cam.
You can try this:
WITH ['Parrot', 'Lion', 'Giraffe'] AS names
MATCH (:Animal { name: head(names)})-[:BELONGS_TO]->(g:Group)
WITH g,names
MATCH (g)<-[:BELONGS_TO]-(a:Animal)
WITH g,collect(a.name) AS animals,names
WHERE ALL (n IN names
WHERE n IN animals)
RETURN g.name, animals,names,size(animals)
See this console: http://console.neo4j.org/r/vd2mba

Sproutcore nested array one to many binding

I have three tiers of objects, each as one to many. I'd like, when a different notebook is selected, that the page and column view elements get cascading updates.
Notebook > Pages > Columns
With notebooksController and notebookController I can bind
App.Notebook = SC.Record.extend({
name: SC.Record.attr(String),
pages: SC.Record.toMany('App.Page', {isMaster: YES, inverse: 'notebook'})
});
App.Page = SC.Record.extend({
pageNumber: SC.Record.attr(Number),
notebook: SC.Record.toOne('App.Notebook', {isMaster: NO, inverse: 'pages'}),
columns: SC.Record.toMany('App.Column', {isMaster: YES, inverse: 'page'})
});
App.Column = SC.Record.extend({
columnNumber: SC.Record.attr(Number),
page: SC.record.toOne('App.Page', {isMaster: NO, inverse: 'columns'})
});
Following this, I can't seem to get the content binding for pagesController to work. I want the contents of pagesController, pageController, columnsController, and columnController to be cascaded down so that when a user clicks a different notebook, the views presented automatically flick across to the correct content.
ArrayController notebooksController
// contents filled from fixture
ObjectController notebookController
// bound to notebooksController selection
ArrayController pagesController
// contentBinding: 'notebookController.pages' does not work!
ObjectController pageController
// bound to pagesController selection
// and down to column
Assuming you have a single Notebook, try
App.notebookController = SC.ObjectController.create({
// call App.notebookController.set('content', aNotebook)
// to set the content on this controller
});
App.pageController = SC.ArrayController.create({
// the notebookController is a proxy to the its content, so you dont need
// 'content' in the binding contentBinding: 'App.notebookController.pages'
// bind the list view to App.pagesController.arrangedObjects. If you look in the code
// arranged objects is a reference to the array controller itself, which has array methods
// on it
});
App.pageSelectionController = SC.ObjectController.create({
// You need to add
//
// selectionBinding: 'App.pageSelectionController.content
//
// to the collection view where you select the page
// you can do this in places to see when things change. This controller is just a proxy
// to the selected page.
selectionDidChange: function(){
console.log('page selection changed to [%#]'.fmt(this.get('content');
}.observes('content')
});
App.columnsController = SC.ArrayController.create({
contentBinding: 'App.pageSelectionController.columns'
// again, where you want to show the columns, bind to
// App.columnsController.arrangedObjects
});
I found the answer.
Turns out you can indeed simply use App.notebookController.pages.
The problem was, my fixtures were not set up correctly. It is not enough to map the child to the parent, the parent has to be mapped back to the child.
I originally had:
Autofocus.Notebook.FIXTURES = [
{ guid: 1, title: "Home Notebook", columnCount: 2},
{ guid: 2, title: "Work Notebook", columnCount: 2}
];
And was all fixed when I did the following:
Autofocus.Notebook.FIXTURES = [
{ guid: 1, title: "Home Notebook", columnCount: 2, pages: [11, 12] },
{ guid: 2, title: "Work Notebook", columnCount: 2, pages: [21, 22]}
];

Resources