Reset 'content' property to null for a cytoscape node - cytoscape.js

So I have found that I cannot reset the content property of a node to null if I have initialized it to any non-null value. In fact, I can't even set it to an empty string or even just whitespace.
simplest example:
cytest = cytoscape({
container : document.getElementById('cy'),
elements : {
nodes : [{data : {id : 'test', name : 'test'}}]
},
style : cytoscape.stylesheet().selector('node')
.css({
content : 'data(name)',
'text-valign' : 'center'
})
})
So that creates a single node with a label 'test', matching its name property. Now, let's try to reset it:
cytest.$('#test').css({'content' : 'hello'})
This works fine, so the property can be changed with the .css call. Initially setting the content property to null also works:
cytest = cytoscape({
container : document.getElementById('cy'),
elements : {
nodes : [{data : {id : 'test', name : 'test'}}]
},
style : cytoscape.stylesheet().selector('node')
.css({
content : null,
'text-valign' : 'center'
})
})
So we can set the property to null, and we can also change the property after setting it. However, we can't do both. We can't change it to null. The following does not work:
cytest.$('#test').css({'content' : null})
Funny enough, neither do these:
cytest.$('#test').css({'content' : ''})
cytest.$('#test').css({'content' : ' '})
So I am trying to find a way I can do this.
The purpose of all of this, in case it matters, is that I attempt to load an image to use as the background-image (this works fine). However, I need to remove the text content if the image exists, and leave the name if it does not. This has to happen after initializing the cytoscape object because there are possibly many icon sets that can be chosen by the user.
So looking for any hints on why it does not work or how I can accomplish what I need. Not pasting in my actual code because it is humongous and this is just one part of it.

You've specified a mapping: label : 'data(name)'. So the label is the name field in the node's data. It then suffices to set node.data('name', '') to clear the label. Or you can use a higher-priority (i.e. later) style block in your stylesheet, e.g.
.selector('node.no-label')
.style({ 'label': '' })
And:
node.addClass('no-label');
Or use a function:
.selector('node.no-label')
.style({ 'label': n => n.data('isLabelShown') ? n.data('name') : '' })
If you play around with style bypasses with node.style(), then you're overriding a style property. Empty override values like null or '' will just delete the override itself -- thereby falling back on what the stylesheet specifies. This is all mentioned in the docs:
You should use this function very sparingly for setting, because it overrides the style of an element, despite the state and classes that it has. In general, it’s much better to specify a better stylesheet at initialisation that reflects your application state rather than programmatically modifying style.
Only defined visual style properties are supported.
If you would like to remove a particular overridden style property, set null or '' (the empty string) to it.
http://js.cytoscape.org/#eles.style

Related

Grouping WSAPI data store by Parent Name

I am creating a rallygrid component and would like to have the grid items grouped by their parent's Name attribute (bonus if I could also display the ID of the parent). I added the groupBy:'Parent' configuration to the storeConfig of the grid and was surprised that no results were returned. I also tried using groupBy:'Parent.Name' but still nothing.
I know this is possible with other fields such as Owner, but I'm at a loss as to why the Parent wouldn't be usable as well. Is this a bug, or am I setting the config up incorrectly?
Thanks
Change the storeConfig to keep the records from trying to update after being grouped:
storeConfig : {
remoteSort : false,
remoteGroup : false,
remoteFilter : false,
}
Add a listener to the load event which assigns a root level property to the record and groups by that record value. (For some reason store.group('Parent.Name'); doesn't work.)
load: function(store) {
store.each(function(record) {
record.set('ParentName', record.get('Parent') && record.get('Parent').Name || '-- Unparented --');
});
store.group('ParentName');
}
I thought it was a bug with the SDK too, but per WS API documentation, Parent, unlike Owner, or Feature is not sortable.
So when I use groupField: 'Parent' the grid is empty, and response showed error:
Ext.data.JsonP.callback6({"QueryResult": {..., "Errors": ["Cannot sort using attribute Parent"]
It is trying to sort by Parent, but Parent attribute is not sortable. So the SDK ran into a WS API limitation.
On a side note, I did not use groupBy, instead I used groupField on the store (in this example I grouped by Kanban field) :
var myStore = Ext.create('Rally.data.WsapiDataStore',{
model: 'UserStory',
groupField: 'c_MyKB',
//...
});
And then used features: [{ftype:'grouping'}] in the grid.
this._myGrid = Ext.create('Ext.grid.Panel', {
store: myStore,
features: [{ftype:'grouping'}],
//...

Getting input name from dijit widget

I'm creating dijit widget from input:
<input name="serviceName" type="text"/>
input = new ValidationTextBox({required: true}, query('[name=serviceName]')[0])
I have a lot of inputs and I want to batch-process them, giving them to separate function.
The problem is, that I can't find in docs, how to get the input name back from widget (which can be also Select, DateBox etc., neither I can find that property inspecting element in Firebug
function processInput(input) {
var inputName = ???
}
I've tried input.name and input.get('name'), but they are returning undefined.
When instantiating your widget programmatically, properties are usually not copied. It's only when you use declarative markup that these properties are copied. This means that the name property is not copied from your original input node, so it's in fact empty.
When creating your ValidationTextBox, just provide the name property like the example below:
var inputNode = query('[name=serviceName]')[0];
var input = new ValidationTextBox({
required: true,
name: inputNode.name
}, inputNode);
You can then retrieve the name with either input.name or input.get('name').
I also made an example JSFiddle.

ComponentQuery for specific listitem

I want to create a controller with a ref to a specific item in a List, is this possible?
So really I need the ComponentQuery to reference a listitem. I am setting the data inline in the list:
Ext.define('MyApp.view.MyView', {
extend : 'Ext.List',
alias : 'widget.mylist',
config : {
title : 'Title',
itemTpl : ['{displayName}'].join(''),
data : [{
displayName : 'One',
id : 1
}, {
displayName : 'Two',
id : 2
}, {
displayName : 'Three',
id : 3
}, {
displayName : 'Four',
id : 4
}]
}
});
Then on in my controller (this is obviously not working):
refs : {
oneListItem: 'mylist.list[id=1]',
twoListItem: 'mylist.list[id=2]',
threeListItem: 'mylist.list[id=3]',
fourListItem: 'mylist.list[id=4]'
}
Hopefully this is possible. If it is not I can listen for an 'itemtap' on the list and call a method from there, however, I would prefer not have to do that for cleanliness.
Thanks in advance.
Brad
First off: adding id to the objects in your data section will not have any effect, that is just the data that's taken and substituted in the itemTpl you specified before.
As to your question: Sencha Touch 2 Documentation does not mention a way to have Ext.ComponentQuery select the n-th child of a Component, so, assuming you want to create your list objects with inline data, the only way I see is to use the id that Sencha automatically applies to each listItem on creation.
These ids are of the #ext-listitem-1 kind, see an example of how to use them in this Sencha Fiddle demo.
This is however bad practice, because you cannot rely on those id, if for example you add another list to your application they can change breaking your code.
I do not see any valid reason why you shouldn't use itemtap to act on the list, since with the event you get all the data you need to manipulate the proper list item:
itemtap( this, index, target, record, e, eOpts )
For completeness sake, I must mention that you could also add your items dynamically with Ext.dataview.List.add(), that way you should be able to create your dataitems with a custom id.
You might try Ext.select('.x-list-item-first'); or Ext.select('.x-list-item-last'); if you want a handle on the first or last item. I would encourage you to add an xtype set to "mylist" instead of using alias.

Hiding a series by default in a spider plot

I have a spider plot in using the graphing library of Dojo defined like this:
require([
"dojox/charting/Chart",
"dojox/charting/themes/Claro",
"dojox/charting/plot2d/Spider",
"dojox/charting/action2d/Tooltip",
"dojox/charting/widget/SelectableLegend",
"dojox/charting/axis2d/Default"
], function (Chart, theme, Spider, Tooltip, Legend, Default) {
var chart = new Chart(element).setTheme(theme).addPlot("default", {
type: Spider,
radius: 200,
fontColor: "black",
labelOffset: "-20"
});
var colors = ["blue", "red", "green", "yellow", "purple", "orange", "teal",
"maroon", "olive", "lime", "aqua", "fuchsia"];
$.each(factors, function (index, factor) {
chart.addAxis(factor.name, {
type: Default,
min: factor.min,
max: factor.max
});
});
$.each(presets, function (pIndex, preset) {
var data = [];
$.each(factors, function (fIndex, factor) {
data[factor.name] = preset.values[fIndex];
});
chart.addSeries(preset.short, data, {
fill: colors[pIndex % colors.length]
});
});
new Tooltip(chart, "default");
chart.render();
new Legend({
chart: chart,
horizontal: false
}, $(element).next(".legend")[0]);
});
I add a series for every member of an array called presets and I use a selectable legend that lets the user turn them on or off as they want. However, what I can't seem to find in the docs is how to start a series in the unselected, not visible state? What I ideally want to do is cap the number of series visible when the page loads because in some cases I have up to 14 presets and it just looks a mess until the user deselects a bunch. So I'd like to have, say, every preset above the first 5 be hidden at the start.
Here's a crude fiddle I've knocked to demonstrate. What I want is to have some of the series unselected when the plot is first displayed.
Update: I tried adding this after adding my series:
var checkboxes = $(".dijitCheckBoxInput").each((index, elem) => {
if (index > 4) {
elem.click();
}
});
Which works, but seems very fragile. If they change the class assigned to checkboxes, it'll break. Also, it prohibits me using more than one set of dojo checkboxes because I don't have a good way to tell the difference. (Note, the IDs of the checkboxes added by the SelectableLegend are dijit_form_CheckBox_0, dijit_form_CheckBox_1, etc, which also gives no useful information as to what they are related to). I thought I might be able to use the legend placeholder div as a way to select the descendant checkboxes, but it appears that Dojo replaces the placeholder entirely with a table.
i looked into the dojo code and found the area in which the shapes are toggled on & off whitin the SelectableLegend.js :
var legendCheckBox = query(".dijitCheckBox", legend)[0];
hub.connect(legendCheckBox, "onclick", this, function(e){
this._toggle(shapes, i, legend.vanished, originalDyn, seriesName, plotName);
legend.vanished = !legend.vanished;
e.stopPropagation();
});
The toggling process is very complex and is based on many local attributes:
_toggle: function(shapes, index, isOff, dyn, seriesName, plotName){
arrayUtil.forEach(shapes, function(shape, i){
var startFill = dyn.fills[i],
endFill = this._getTransitionFill(plotName),
startStroke = dyn.strokes[i],
endStroke = this.transitionStroke;
if(startFill){
if(endFill && (typeof startFill == "string" || startFill instanceof Color)){
fx.animateFill({
shape: shape,
color: {
start: isOff ? endFill : startFill,
end: isOff ? startFill : endFill
}
}).play();
}else{
shape.setFill(isOff ? startFill : endFill);
}
}
if(startStroke && !this.outline){
shape.setStroke(isOff ? startStroke : endStroke);
}
}, this);
}
I tried also checking & unchecking the dijit/form/Checkbox in a legend manually, but that does not trigger the _toggle function in any case, even if you do a render() / fullrender() on the chart.
With that in mind it seems that there is no other possibilty to toggle the series on and off than by firing the onclick events manually.
To make your code less fragile, you could access the Checkbox widgets within the legend manually using:
query(".dijitCheckBox", legend); // Should deliver an array containing
the widgets.
and triggering the onclick event on them. Their keynumber in the array should correspond to the order the series where added...
Dojo is a fine piece of work, please dont stop working with it !
dojox/charting/Series has an attribute called dirty which according to the API docs is a "flag indicating whether or not this element needs to be rendered".
Alternately, if you are limiting the display of some series you can write a separate interface for adding them. For example, loop over the first 5. Then create a select box or list of check boxes with all entries and an onchange event that calls chart.addSeries.
Keeping a reference to each series you create will allow you to later call destroy() or destroyRecursive() on it if the user no longer wishes it displayed.
So while ideally you could toggle the display of these series, the worst case senerio is that you just add, destroy, and read based on some user input.
Using a templated widget will allow you to keep this interface and the chart tightly linked and support reuse.
BTW, consider using "dojo/_base/array" and "dojo/query" in place of the jquery
I think i've got it !
I found another way to access the checkboxes ! It's the same way dojo uses internally to connect the "toggle code" to the onclick event. First take a look at this from SelectableLegend.js (Lines 150 - 156):
// toggle action
var legendCheckBox = query(".dijitCheckBox", legend)[0];
hub.connect(legendCheckBox, "onclick", this, function(e){
this._toggle(shapes, i, legend.vanished, originalDyn, seriesName, plotName);
legend.vanished = !legend.vanished;
e.stopPropagation();
});
It looks like they use the ".dijitCheckBox" class to find the checkbox dom element and connect to it using dojo/connect. Now based on that, i made this function:
function toggleSeries (legend,num) {
dojo.query("*",legend.legends[num])[0].click();
dijit.findWidgets(legend.legends[num])[0]._onClick(); }
It doesn't use any class definition (because of the *) and it accesses the areas where the checkboxes are from within the SelectableLegend. It needs the SelectableLegend and the number of the series you want to deactivate as parameters. Here the jsfiddle example with this function & hiding all 4 of your series with it:
http://jsfiddle.net/luciancd/92Dzv/17/
Also please notice the "onDomReady" Option in jsfiddle, without it: doesnt work in IE.
And the ready function within the code !
Lucian
I have updated your code http://jsfiddle.net/92Dzv/18/
Here is the key to toogle.
dom.byId(le._cbs[0].id).click();
dom.byId(le._cbs[2].id).click();
Choose the index of your legend and set to _cbs.
By this way le._cbs[0].id you will get the real id of checkbox (that inside in the widget) and then just use click()
Note : le is came from here.
var le = new Legend({
chart: chart,
horizontal: false
}, legend);

Set a default UI across all components in Sencha Touch

Within Sencha Touch, is it possible to define a default UI , like "light" or "dark", that applies to all components (unless overwritten explicitly)?
The aim is to avoid having to declare ui: "dark", or any custom UI that is made, for every element.
Cheers!
You can try this:
Ext.apply(Ext.Component.prototype, {
getUi: function() {
var defaultUi = 'light';
// value of [this.config.ui] is ignored here
// we can use something like forcedUi
return (this.forcedUi) ? this.forcedUi : defaultUi;
}
})
The disadvantage of this code is that we need to specify another variable for applying ui different from 'light' (because variable 'ui' via getUi() will always return 'light'):
...
items: [{
xtype: 'button',
forcedUi: 'dark'
}]
...
I am stuck on Touch 1.1 so sunsay's solution didn't work for me, but this did:
Ext.CustomToolbar = Ext.extend(Ext.Toolbar,
{
ui:'app'
});
Ext.reg('toolbar', Ext.CustomToolbar);
So, it's still component-by-component-type, but not component-by-component-instance. And since you can overwrite the "reg", no need for custom x-types all over the place, either.
I assume that you know about sencha touch styles and themes. Otherwise you can download a pdf file from this link which clearly describes about how to do it...
http://f.cl.ly/items/d9df79f57b67e6e876c6/SenchaTouchThemes.pdf
In it they are mentioning about scss file where you can specify the base-color, ie
$base-color: #4bb8f0 ;
$base-gradient: 'glossy';
Then run it ... you can see the toolbars and buttons created with the color and gradient you have mentioned.