How to add (remove) new items to an existing dataSource in Dashcode? - datasource

I have a question concerning DashCode and dataSources: I have defined an JSON object in javascript file, linked it to a dataSource and connected the company names to the user interface (a 'list' element). The JSON object looks like:
{
items: [
{ company:'A', product:'a1', color:'red' },
{ company:'B', product:'b2', color:'blue' },
{ company:'C', product:'c3', color:'white' }
]
}
How do I programmatically add (or remove) an additional "item" to the existing dataSource? I have used the following approach:
function addElement() {
var newElement = [{company:'D', product:'d4', color:'yellow' }];
var ds = dashcode.getDataSource('list');
ds.arrangedObjects.addObject(newElement);
}
and
function delElement()
{
var ds = dashcode.getDataSource('list');
if (ds.hasSelection()) {
var index = ds.selectionIndex();
ds.arrangedObjects.removeObjectAtIndex(index);
}
}
This piece of code indeed adds (removes) an additional item to the dataSource. However, when I use the list.filterpredicate to search the list for the new item, the new item is ignored.
What is the 'correct' approach to add (or remove) an item to an existing dataSource programmatically?
Your help is being appreciated!

Here is an answer to the question:
1) Define a KVO object in main.js. This step is important to make the object bindable:
anObj = Class.create(DC.KVO, {
constructor: function(company, product, color) {
this.company = company;
this.product = product;
this.color = color;
}
});
2) The updated version of the function 'addElement' is:
function addElement() {
var newElement = new anObj('D', 'd4', 'yellow');
var ds = dashcode.getDataSource('list');
var inventory = ds.valueForKeyPath('content');
inventory.addObject(newElement);
}
3) The updated version of the function 'removeElement' looks similar:
function delElement()
{
var ds = dashcode.getDataSource('list');
if (ds.hasSelection()) {
var index = ds.selectionIndex();
var inventory = ds.valueForKeyPath('content');
inventory.removeObjectAtIndex(index);
}
}
I hope this information helps!

Related

get is not defined when trying to extends JSONSerializer

I try to define my custom serializer by extending DS.JSONSerialzer.
I pick the serialize function without modifications but when i run Ember,i get this error:
ReferenceError: get is not defined
This is my code :
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
serialize: function(record, options) {
var json = {};
if (options && options.includeId) {
var id = get(record, 'id');
if (id) {
json[get(this, 'primaryKey')] = id;
}
}
record.eachAttribute(function(key, attribute) {
this.serializeAttribute(record, json, key, attribute);
}, this);
record.eachRelationship(function(key, relationship) {
if (relationship.kind === 'belongsTo') {
this.serializeBelongsTo(record, json, relationship);
} else if (relationship.kind === 'hasMany') {
this.serializeHasMany(record, json, relationship);
}
}, this);
return json;
},
});
I didn't change any code. This is the original. Why get is suddenly undefined? It's imported in line 1 in the original file JSONSerialiser
Can you help me?
They have get defined in the scope when creating the serializer, but that doesn't extend outside of their scope into your files.
var get = Ember.get;
var isNone = Ember.isNone;
var map = Ember.ArrayPolyfills.map;
var merge = Ember.merge;
Either replace all of the get methods with Ember.get or define get to be Ember.get

Is it possible to filter data in a dgrid like you can in a datagrid? If so, how?

I'm relatively new to dojo and have seen how datagrid offers a dynamic filtering capability that reduces the visible rows based on what you type into a filter text input. I have not found any examples of how to do it with the dgrid. If it can be done, please provide an example or point me to a resource that offers a tutorial or example. Thanks!
Yes, it is possible. Use dgrid/OnDemandGrid and define query function that will return true or false based on your logic for each row in dojo/store powering the grid.
I prepared an example to play with at jsFiddle: http://jsfiddle.net/phusick/7gnFd/, so I do not have to explain too much:
The Query Function:
var filterQuery = function(item, index, items) {
var filterString = filter ? filter.get("value") + "" : "";
// early exists
if (filterString.length < 2) return true;
if (!item.Name) return false;
// compare
var name = (item.Name + "").toLowerCase();
if (~name.indexOf(filterString.toLowerCase())) { return true;}
return false;
};
The Grid:
var grid = new Grid({
store: store,
query: filterQuery, // <== the query function for filtering
columns: {
Name: "Name",
Year: "Year",
Artist: "Artist",
Album: "Album",
Genre: "Genre"
}
}, "grid");
I know this isn't the answer to the question asked, and the answer provided is masterful and we use it quite a bit.
However, this functionality doesn't seem to work well at all if you're using a TreeGrid (columns with the "dgrid/tree" plugin). I've written some code to simulate the same behavior as the accepted answer with a tree grid. It's basically just looping through the items in the store and hiding any row elements that don't match whatever condition you set forth. Thought I would share it in case it helps anybody. It's rather ugly and I'm sure it can be improved upon, but it works.
It basically uses the same concept as phusick's answer. You need to watch a value on a TextBox, but instead of refreshing the grid you have it call a function:
textBox.watch("value", lang.hitch(this, function() {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
};
timeoutId = setTimeout(lang.hitch(this, function() {
this.filterGridByName(textBox.get('value'), myGrid);
}, 300));
}));
And here's the function:
filterGridByName: function(name, grid){
try {
for (var j in grid.store.data){
var dataItem = grid.store.data[j];
var childrenLength = dataItem.children.length;
var childrenHiddenCount = 0;
var parentRow = grid.row(dataItem.id);
for (var k in dataItem.children){
var row = grid.row(dataItem.children[k].id);
var found = false;
if (dataItem.children[k].name.toLowerCase().indexOf(name.toLowerCase()) != -1){
found = true;
}
if (found){
if (row.element){
domStyle.set(row.element, "display", "block");
}
if (parentRow.element){
domStyle.set(parentRow.element, "display", "block");
}
} else {
childrenHiddenCount++;
// programmatically uncheck any hidden item so hidden items
for (var m in grid.dirty){
if (m === dataItem.children[k].id && grid.dirty[m].selected){
grid.dirty[m].selected = false;
}
}
if (row.element){
domStyle.set(row.element, "display", "none");
}
}
}
// if all of the children were hidden, hide the parent too
if (childrenLength === childrenHiddenCount){
domStyle.set(parentRow.element, "display", "none");
}
}
} catch (err){
console.info("error: ", err);
}
}

In an ExtJS Grid, how do I get access to the data store fields that are part of the sort set

How do I get access to the columns/datastore fields that are part of the sort set.
I am looking to modify the a grid's sort parameters for remote sorting. I need the remote sort param's sort key to match the column's field's mapping property. I need these things to happen though the normal 'column header click sorts the data' functionality.
Remote sorting and field mapping (ExtJS 4.1)
This functionality seems not to be implemented in ExtJS. Here is a solution using the encodeSorters function provided since ExtJS 4. Accessing fields map throught the model's prototype is a bit dirty but it does the job :
var store = Ext.create('Ext.data.Store', {
...,
proxy: {
...,
encodeSorters: function (sorters) {
var model = store.proxy.model,
map = model.prototype.fields.map;
return Ext.encode(Ext.Array.map(sorters, function (sorter) {
return {
property : map[sorter.property].mapping || sorter.property,
direction: sorter.direction
};
}));
}
}
});
However, it would be more relevant to override the original method :
Ext.data.proxy.Server.override({
encodeSorters: function(sorters) {
var min, map = this.model.prototype.fields.map;
min = Ext.Array.map(sorters, function (sorter) {
return {
property : map[sorter.property].mapping || sorter.property,
direction: sorter.direction
};
});
return this.applyEncoding(min);
}
});
Assuming you are using simpleSortMode, you could do something like this in your store.
listeners: {
beforeload: function( store, operation, eOpts ) {
if (store.sorters.length > 0) {
var sorter = store.sorters.getAt(0),
dir = sorter.direction,
prop = sorter.property,
fields = store.model.getFields(),
i,
applyProp = prop;
for (i = 0; i < fields.length; i++) {
if (fields[i].name == prop) {
applyProp = fields[i].mapping || prop;
break;
}
}
//clearing the sorters since the simpleSortMode is true so there will be only one sorter
store.sorters.clear();
store.sorters.insert(0, applyProp, new Ext.util.Sorter({
property : applyProp,
direction: dir
}));
}
}
},

Filtering epics from Kanban board

I would like to start by saying I have read Rally Kanban - hiding Epic Stories but I'm still having trouble on implementing my filter based on the filter process from the Estimation Board app. Currently I'm trying to add an items filter to my query object for my cardboard. The query object calls this._getItems to return an array of items to filter from. As far as I can tell the query calls the function, loads for a second or two, and then displays no results. Any input, suggestions, or alternative solutions are welcomed.
Here's my code
$that._redisplayBoard = function() {
that._getAndStorePrefData(displayBoard);
this._getItems = function(callback) {
//Build types based on checkbox selections
var queries = [];
queries.push({key:"HierarchicalRequirement",
type: "HierarchicalRequirement",
fetch: "Name,FormattedID,Owner,ObjectID,Rank,PlanEstimate,Children,Ready,Blocked",
order: "Rank"
});
function bucketItems(results) {
var items = [];
rally.forEach(queries, function(query) {
if (results[query.key]) {
rally.forEach(results[query.key], function(item) {
//exclude epic stories since estimates cannot be altered
if ((item._type !== 'HierarchicalRequirement') ||
(item._type === 'HierarchicalRequirement' && item.Children.length === 0)) {
items = items.concat(item);
}
});
}
});
callback(items);
}
rallyDataSource.findAll(queries, bucketItems);
};
function displayBoard() {
artifactTypes = [];
var cardboardConfig = {
types: [],
items: that._getItems,
attribute: kanbanField,
sortAscending: true,
maxCardsPerColumn: 200,
order: "Rank",
cardRenderer: KanbanCardRenderer,
cardOptions: {
showTaskCompletion: showTaskCompletion,
showAgeAfter: showAgeAfter
},
columnRenderer: KanbanColumnRenderer,
columns: columns,
fetch: "Name,FormattedID,Owner,ObjectID,Rank,Ready,Blocked,LastUpdateDate,Tags,State,Priority,StoryType,Children"
};
if (showTaskCompletion) {
cardboardConfig.fetch += ",Tasks";
}
if (hideLastColumnIfReleased) {
cardboardConfig.query = new rally.sdk.util.Query("Release = null").or(kanbanField + " != " + '"' + lastState + '"');
}
if (filterByTagsDropdown && filterByTagsDropdown.getDisplayedValue()) {
cardboardConfig.cardOptions.filterBy = { field: FILTER_FIELD, value: filterByTagsDropdown.getDisplayedValue() };
}
cardboardConfig.types.push("HierarchicalRequirement");
if (cardboard) {
cardboard.destroy();
}
artifactTypes = cardboardConfig.types;
cardboard = new rally.sdk.ui.CardBoard(cardboardConfig, rallyDataSource);
cardboard.addEventListener("preUpdate", that._onBeforeItemUpdated);
cardboard.addEventListener("onDataRetrieved", function(cardboard,args){ console.log(args.items); });
cardboard.display("kanbanBoard");
}
};
that.display = function(element) {
//Build app layout
this._createLayout(element);
//Redisplay the board
this._redisplayBoard();
};
};
Per Charles' hint in Rally Kanban - hiding Epic Stories
Here's how I approached this following Charles' hint for the Rally Catalog Kanban. First, modify the fetch statement inside the cardboardConfig so that it includes the Children collection, thusly:
fetch: "Name,FormattedID,Children,Owner,ObjectID,Rank,Ready,Blocked,LastUpdateDate,Tags,State"
Next, in between this statement:
cardboard.addEventListener("preUpdate", that._onBeforeItemUpdated);
And this statement:
cardboard.display("kanbanBoard");
Add the following event listener and callback:
cardboard.addEventListener("onDataRetrieved",
function(cardboard, args){
// Grab items hash
filteredItems = args.items;
// loop through hash keys (states)
for (var key in filteredItems) {
// Grab the workproducts objects (Stories, defects)
workproducts = filteredItems[key];
// Array to hold filtered results, childless work products
childlessWorkProducts = new Array();
// loop through 'em and filter for the childless
for (i=0;i<workproducts.length;i++) {
thisWorkProduct = workproducts[i];
// Check first if it's a User Story, since Defects don't have children
if (thisWorkProduct._type == "HierarchicalRequirement") {
if (thisWorkProduct.Children.length === 0 ) {
childlessWorkProducts.push(thisWorkProduct);
}
} else {
// If it's a Defect, it has no children so push it
childlessWorkProducts.push(thisWorkProduct);
}
}
filteredItems[key] = childlessWorkProducts;
}
// un-necessary call to cardboard.setItems() was here - removed
}
);
This callback should filter for only leaf-node items.
Mark's answer caused an obscure crash when cardboard.setItems(filteredItems) was called. However, since the filtering code is actually manipulating the actual references, it turns out that setItems() method is actually not needed. I pulled it out, and it now filters properly.
Not sure this is your problem but your cardboard config does not set the 'query' field. The fetch is the type of all data to retrieve if you want to filter it you add a "query:" value to the config object.
Something like :
var cardboardConfig = {
types: ["PortfolioItem", "HierarchicalRequirement", "Feature"],
attribute: dropdownAttribute,
fetch:"Name,FormattedID,Owner,ObjectID,ClassofService",
query : fullQuery,
cardRenderer: PriorityCardRenderer
};
Where fullQuery can be constructed using the the Rally query object. You find it by searching in the SDK. Hope that maybe helps.

Expanding all nodes in dijit.Tree

Is there a good way to expand/close all the expandable nodes in a dijit.Tree?
For those looking for an answer, put this in your initializing code:
var treeControl = new dijit.Tree({
model: treeModel,
expandAll: function() {
// summary:
// Expand all nodes in the tree
// returns:
// Deferred that fires when all nodes have expanded
var _this = this;
function expand(node) {
_this._expandNode(node);
var childBranches = dojo.filter(node.getChildren() || [], function(node) {
return node.isExpandable;
});
var def = new dojo.Deferred();
defs = dojo.map(childBranches, expand);
}
return expand(this.rootNode);
}
});
At least, that works for me. And you can do the same with collapseAll() where you only need to switch _this._expandNode(node); with _this._collapseNode(node);
Yes, autoExpand=true (as an initialization parameter to the Tree).
If you need to expand/collapse dynamically, Tree used to have a method for that, but I took it out. However, you can copy it from: http://bugs.dojotoolkit.org/changeset/20529.
To collapse all nodes... ( remember to NOT collapse the root node when it is not shown( I like to have multiple items shown for my trees ))
_collapseAllTreeNodeContainers: function(){
var _tree = _this;
function collapse(node) {
// never collapse root node, otherwise hides whole tree !
if ( _tree.showRoot == false && node != _tree.rootNode ) {
_tree._collapseNode(node);
}
var childBranches = dojo.filter(node.getChildren() || [], function(node) {
return node.isExpandable;
});
var def = new dojo.Deferred();
defs = dojo.map(childBranches, collapse);
}
return collapse( _tree.rootNode);
}
You could use on the dijit/Tree instance the following methods:
tree.expandAll();
http://dojotoolkit.org/api/?qs=1.10/dijit/Tree#1_10dijit_Tree_expandAll
or
tree.collapseAll();
http://dojotoolkit.org/api/?qs=1.10/dijit/Tree#1_10dijit_Tree_collapseAll