ExtJS Ajax problems for grid panel - javascript

I have a grid Panel in my code as:
Ext.create('Ext.grid.Panel', {
id : 'frPanel-' + interfaceId,
store : frStore,
columns : [
{
text : 'Sequence',
dataIndex : 'ruleId',
menuDisabled : true
},
{
text : 'Source',
dataIndex : 'source',
renderer : function(value, metaData) {
var newValue = convertObjValue(value);
if (newValue.match(/[-]+/i)) {
metaData.tdAttr = 'data-qtip="'
+ networkStore(value) + '"';
}
return newValue;
}
},
// paging bar at the bottom
dockedItems : [ {
xtype : 'pagingtoolbar',
store : frStore, // same store GridPanel is using
dock : 'bottom',
displayInfo : true
} ],
height : 300,
width : '100%',
forceFit : true,
renderTo : 'frContainer-' + interfaceId
});
And these are the helper function i have:
// To get the value after 2nd colon for object and object-group
function convertObjValue(value) {
var result;
var exp = /.*?:.*?:(.*)/i;
var newValue = value;
if ((result = exp.exec(value)) != null) {
if (result.index === exp.lastIndex) {
exp.lastIndex++;
}
newValue = result[1];
}
return newValue;
}
The store:
function networkStore(value) {
//var store = Ext.create('Ext.data.Store', {
var store = new Ext.data.Store({
model : 'networkModel',
autoLoad : {
timeout : 60000
},
proxy : {
type : 'ajax',
url : networkObjsURL + "&" + Ext.urlEncode({
'peId' : value
}),
reader : {
type : 'json',
idProperty : 'objValue'
},
}
});
var hoverOutput = "";
if(store.data.length > 0){
store.data.items.forEach(function(item) {
hoverOutput += item.data.objectValue + "</br>";
});
}
console.log(hoverOutput);
return hoverOutput;
and last but not the least is the model:
Ext.define('networkModel', {
extend : 'Ext.data.Model',
fields : [ {
name : 'objectValue'
} ]
});
Now comes the issue. The problem is when i dont place the breakpoint in the browser in store, the values wont show up in qtip. Im guessing thats because of the grid panel not waiting for the response back from the store after ajax response. Can someone help me figure out a workaround for this situation?
Thanks in advance

Have you tried setting
autoLoad:false
and then something like :
store.load({
callback: function(records, operation, success) {
if (success == true) {
//do your stuff
var hoverOutput = "";
if(store.data.length > 0){
store.data.items.forEach(function(item) {
hoverOutput += item.data.objectValue + "</br>";
});
}
console.log(hoverOutput);
return hoverOutput;
} else {
// the store didn't load, deal with it
}
}
// scope: this,
});
Since your breakpoint allows you to see your data, im thinking you are right in assuming it's a delay issue. Since Ext is asynchronous, it wont wait for the ajax call to be finished before continuing it's processing. A callback will help you manage this as it will be called when ajax returns.
I'm still also fairly new to Ext but at least that's my understanding. Hope it helps, or at least points you in the right way.
Edit because i reminded that sometimes having a return inside the success will make it hard to debug, etc. So you could also try changing your success to call another function and have that function do the processing, just keep the scope in mind.

I asked in comment which version ExtJS you're using but I didn't get response so I assume that you are using ExtJS 5.
Control Flow in your code is strange for me.
Why do you create store in render function indirectly (directly in networkStore) multiple times?
Nevertheless, store is fetching data asynchronous, so you have to wait/callback result (or use Future/Promise API for example). In addition, you should have any necessary data for the grid in frStore store (which you pass to the grid). You can also take advantage of data association in your model or you can create new field in model with convert function and use value of the association/field in render function.
Let me show you one of the approach how to do that (a simple one).
ExtJS doesn't like modifying records inside render function so we prepare a model which has a necessary qtip value.
I assume that you can load data of networkStore earlier (autoload: true) but this is for simplicity and you can change it later, for example using remoteFilter and callbacks.
You don't show definition of frStore and underlying model so I will use FrStore and FrModel as class names.
Ext.define('FrModel', {
extend: 'Ext.data.Model',
// ...
fields: [
// source field
// ...
/** qtip value **/
{
name: 'qtip',
type: 'string',
convert: function (value, record) {
var result = '';
// below code is from your render function with modifications
if (record.get('rendered_source').match(/[-]+/i)) {
result = 'data-qtip="'
+ networkStore(record.get('source')) + '"';
}
return result;
},
depends: ['source', 'rendered_source']
},
/** rendered source **/
{
name: 'rendered_source',
type: 'string',
convert: function (value, record) {
var newValue = convertObjValue(record.get('source'));
return newValue;
},
depends: ['source']
}
]
// ...
}
After that change render function is simple:
// ...
{
text : 'Source',
dataIndex : 'rendered_source', // this will allow users to sort & filter this field by the values which are displayed
renderer : function(value, metaData, record) {
metaData.tdAttr = 'data-qtip="'
+ record.get('qtip') + '"';
}
return value;
}
},
// ...
You can need also a NetworkStore which you could place in seperate file: (I prefer proxy/schema in model but I've used your code)
Ext.create('Ext.data.Store', { // using Ext.create is better
model : 'networkModel',
storeId: 'networkStore', // registering store in Ext.data.StoreManager in order to get later this store by Ext.getStore(<store_id>)
autoLoad : true,
proxy : {
type : 'ajax',
url : networkObjsURL, // we load all records but I mentioned earlier that you can change this
reader : {
type : 'json',
idProperty : 'objValue'
},
}
});
I added peId field in netowrkModel because we want to query store later.
Ext.define('networkModel', {
extend : 'Ext.data.Model',
fields : [
{
name: 'objectValue'
},
{
name: 'peId',
type: 'int'
}
]
});
The last part is the networkStore function:
function networkStore(value) {
var store = Ext.getStore('networkStore');
var records = store.query('peId', value, false, true, true);
var hoverOutput = "";
if (records.length > 0) {
records.each(function(item) {
hoverOutput += item.get('objectValue') + "</br>";
});
}
console.log(hoverOutput);
return hoverOutput;
}
PS. I do not test above code.
However, IMO correct solution uses associations. I recommend you read this doc.
You should get to know concepts like schema, proxy, association and others.
When you join FrModel with NetworkModel by peId then you won't need NetworkStore and you build qtip in convert function based on that association.

Related

Copy value of date field in YouTrack

I want to copy the value from issues in one project to issues in another that depend on it.
That is what I have:
var entities = require('#jetbrains/youtrack-scripting-api/entities');
var workflow = require('#jetbrains/youtrack-scripting-api/workflow');
exports.rule = entities.Issue.onChange({
// TODO: give the rule a human-readable title
title: 'Date-propagation',
guard: function(ctx) {
var links = ctx.issue.links['depends on'];
return ctx.issue.isChanged("Date") || !links.added.isEmpty() || !links.removed.isEmpty();
},
action: function(ctx) {
var issue = ctx.issue;
var links = issue.links['depends on'];
function updateIssue(normalIssue){
normalIssue.fields.DueDate = issue.fields.Date.value;
}
function checkList(list){
if(list.isNotEmpty())list.forEach(function(normalIssue){updateIssue(normalIssue);}) ;
}
//checkList(links.removed);
checkList(links);
// TODO: specify what to do when a change is applied to an issue
},
requirements: {
Date: {
type: entities.Field.dateType,
},
Depend: {
type: entities.IssueLinkPrototype,
outward: 'is required for',
inward: "depends on"
}
}
});
The problem is in this line:
normalIssue.fields.DueDate = issue.fields.Date;
How should it be done?
Most probably, you do not have a 'DueDate' field on your instance (as the default field is called 'Due Date'). If so, your code line should look like this:
normalIssue.fields['Due Date'] = issue.fields.Date;

Get all values from a specific kendo grid column

I need get all data from one column of a kendo grid, I've searched a lot but didn't found something useful. I create a kendo grid based on a JSON coming from API API/LogService/ReadAllLog. I'm using AngularJS anyway.
My code:
$scope.gridColumns = [{
field: "SystemName",
editable: false,
title: _t("Title.SystemName"),
allownull: false,
width: 100
}, {
field: "FormName",
editable: false,
title: _t("CommonTitle.SystemFeatureForm"),
allownull: false,
width: 100
}]
and then I fill it like this:
$scope.gridConfig = {
autoBind: true,
inlineOperationalUrl: {
read: {
url: webAccess + "api/LogService/ReadAllLog",
}
}
};
and this is my grid in my view (note: pn-gridview is a custom directive created from angular grid view with some changes):
<pn-gridview id="SystemsGrid"
config="gridConfig"
columns="gridColumns"
</pn-gridview>
Collect all values on one column with a simple helper function:
function getColumnValues(selector, columnName) {
//Init
var columnData = [];
var data = $(selector).data("kendoGrid").dataSource._data;
//collect each valueof given columnName
for (i = 0; i < data.length; i++) {
if (typeof data[i][columnName] !== "undefined") {
columnData.push(data[i][columnName]);
}
}
//return column data as array
return columnData;
}
> DEMO FIDDLE
In your case you need to call this helper function like:
var myColumnData = getColumnValues('#SystemsGrid', 'SystemName');
You can access a specific row data with:
var myRowData = $("#SystemsGrid").data().kendoGrid.dataSource.at(index);

How to setup a mongo projection AFTER a server side transform function in Meteor.js?

I need to limit the number of fields sent to the client from a publish function after applying a transform that requires access to the original doc.
I'm basically trying to avoid sending potentially huge arrays down to the client, and run a bunch of checks to return a nice neat object to work with.
Heres the function I've got now - it works, just the not the way I'd like, basically limiting the fields given to observe function. is there a way to add the projection after the observe / transform.
Meteor.publish('network', function() {
var self = this;
// get the user values initially
var user = Meteor.users.findOne(self.userId);
var followingUsers = user.following ? user.following.users || [] : [];
var followingChannels = user.following ? user.following.channels || [] : [];
var transformMedia = function(doc) {
// get the user each time to keep this publication reactive
votesUp = doc.votes ? doc.votes.up || [] : [];
votesDown = doc.votes ? doc.votes.down || [] : [];
favourites = doc.votes ? doc.votes.favourites || [] : [];
doc.userActions = {
votedUp: _.contains(votesUp, doc._id) ? 1 : 0,
votedDown: _.contains(votesDown, doc._id) ? 1 : 0,
isFavourite: _.contains(favourites, doc._id) ? 1 : 0,
played: _.contains(doc.played, self.userId) ? 1 : 0,
};
return doc;
};
var networkQuery = Media.find({
$and: [
{
$and: [
{processedAt: { $exists: true} },
{processedStatus: 'successful'},
{publishStatus: 'published'}
]
},
{
// if created by this user, user they follow or channels they subscribe to
$or: [
{createdBy: self.userId },
{createdBy: { $in: followingUsers} },
{channels: { $in: followingChannels} },
]
}
// TODO : add not banned or trashed once implemented
]
}, mediaModifiers).observe({
added: function(doc) {
self.added('media', doc._id, transformMedia(doc));
},
changed: function(doc, oldDoc) {
self.changed('media', doc._id, transformMedia(doc));
},
removed: function(doc) {
self.removed('media', doc._id, transformMedia(doc));
},
});
self.onStop(function() {
networkQuery.stop();
});
self.ready();
});
I had a similar issue once. I dealt with it using cursor.observe()+ a custom function (as you did) and I just added a _.pick() to filter the unnecessary fields. Have a look at this publication code for an example (the white list docToPublish part especially):
var self = this;
// Modify the document we are sending to the client.
function filter(doc) {
var length = doc.item.length;
// White list the fields you want to publish.
var docToPublish = _.pick(doc, [
'someOtherField'
]);
// Add your custom fields.
docToPublish.itemLength = length;
return docToPublish;
}
var handle = myCollection.find({}, {fields: {item:1, someOtherField:1}})
// Use observe since it gives us the the old and new document when something is changing.
// If this becomes a performance issue then consider using observeChanges,
// but its usually a lot simpler to use observe in cases like this.
.observe({
added: function(doc) {
self.added("myCollection", doc._id, filter(doc));
},
changed: function(newDocument, oldDocument)
// When the item count is changing, send update to client.
if (newDocument.item.length !== oldDocument.item.length)
self.changed("myCollection", newDocument._id, filter(newDocument));
},
removed: function(doc) {
self.removed("myCollection", doc._id);
});
self.ready();
self.onStop(function () {
handle.stop();
});
This code is borrowed from #datacarl answer to my topic mentioned above.
Note that if you scale up to several servers, the cons of this approach is that each server will have to run the cursor.observe() function.
You also forgot to make your publication ready and dispose of your observers at the end of your publication (it might be because you didn't paste all the pub). It would look like this :
self.ready();
self.onStop(function () {
networkQuery.stop();
});

Creating a Web-based Data Charting Component using Envision.js with Stock Price Ticker feed data

I am trying to create a web app for Data charting component using Envision.js.
The data being used is a Stock Price Ticker feed in JSON format.
I have formed a standard finance line chart (Envision Finance template) using ajax jquery call for getting ticker data and applying the data in the X-Y axis.
But requirement is to create a realtime chart which will update automatically over time with the Stock data with all the child graph details.
Below is the code for the stock chart application :
(function ajax_demo (container) {
// Get initial data
$.getJSON('static/stockTicker.json', function (initialData) {
var
currentData = initialData,
options, finance;
options = {
container : container,
data : {
price : currentData.price,
volume : currentData.volume,
summary : currentData.summary
},
trackFormatter : function (o) {
var
index = o.index,
value;
value = currentData.data[index].date + ': $' + currentData.price[index][1] + ", Vol: " + currentData.volume[index][1];
return value;
},
// An initial selection
selection : {
data : {
x : {
min : 0,
max : 250
}
}
},
// Override some defaults.
// Skip preprocessing to use flotr-formatted data.
defaults : {
volume : {
skipPreprocess : true
},
price : {
skipPreprocess : true
},
summary : {
skipPreprocess : true,
config : {
xaxis : {
// Set x ticks manually with defaults override:
ticks : currentData.summaryTicks
}
}
}
}
};
// Set the selection callback:
options.selectionCallback = (function () {
var data = {
initial : initialData,
fetched : null
};
// Helper for fetching high resolution data
function fetchData (o) {
$.getJSON('static/stockSample.json', function (fetchedData) {
data.fetched = fetchedData;
currentData = fetchedData;
finance.price.options.data = data.fetched.price;
finance.volume.options.data = data.fetched.volume;
_.each(finance.selection.followers, function (follower) {
follower.trigger('zoom', o);
}, this);
});
}
// Selection callback:
return function (selection) {
if (finance) {
var
x = selection.data.x;
if (x.max !== null && Math.abs(x.max - x.min) < 250) {
if (data.fetched) {
// Use high resolution data, if available
finance.price.options.data = data.fetched.price;
finance.volume.options.data = data.fetched.volume;
currentData = data.fetched;
} else {
// Fetch high resolution data
fetchData(selection);
}
} else {
// Use low resolution data
finance.price.options.data = data.initial.price;
finance.volume.options.data = data.initial.volume;
currentData = data.initial;
}
}
}
})();
finance = new envision.templates.Finance(options);
});
}
)(document.getElementById("Demo"));
I couldn't get any example where we can integrate a stock chart in Envision.js with dynamic stock price data updated with specific time.
Shall I use Spring mvc or normal servlet to get it working?
Please help!
You may use the feature "Refresh after a particular time interval".

How to rollback nodes that couldn't be moved in jstree

I'm trying to figure out how to rollback only a folder node that wasn't successfully moved. The code below is an example of what I'm trying to do. The problem comes when you have selected a couple of folders and moved them into another folder. If one of the directories fails to be moved I want to be able to roll it back to it's original parent.
Unfortunately $.jstree.rollback(data.rlbk); rollsback all of the folders that were selected to their previous locations.
$("#tree").jstree({...}).bind("move_node.jstree", function (e, data) {
// process all selected nodes directory
data.rslt.o.each(function (i) {
// Send request.
var move = $.parseJSON($.ajax({
url: "./jstree.php",
type: 'post',
async: false,
data: {
operation: "move_dir",
....
}
}).responseText);
// When everything's ok, the reponseText will be {success: true}
// In all other cases it won't exist at all.
if(move.success == undefined){
// Here I want to rollback the CURRENT failed node.
// $.jstree.rollback(data.rlbk); will rollback all
// of the directories that have been moved.
}
}
});
Is there a way for this to be done?
I've looked at using jstree before, but haven't used it in my code. As a result, the code may not be correct, but the concepts should be.
Based on your code, it appears that you're performing the move operation on the server side and you want the tree to be updated to reflect the results.
Based on the jsTree documentation, it looks as though you cannot commit node updates and roll back to the last commit.
Instead of rolling back only the changes that you don't want, you can roll back the tree (all changes) and perform the moves afterward.
In order to better understand the code below, you may want to read it (or create a copy) without the lines where "wasTriggeredByCode" is set or referenced in the condition for an "if" statement.
$("#tree").jstree({...}).bind("move_node.jstree", function (e, data) {
var jsTree = $(this);
var successes = [];
// Becomes true when function was triggered by code that updates jsTree to
// reflect nodes that were successfully moved on the server
var wasTriggeredByCode = false;
// process all selected nodes directory
data.rslt.o.each(function (i) {
// I'm not certain that this is how the node is referenced
var node = $(this);
wasTriggeredByCode = (wasTriggeredByCode || node.data('redoing'));
// Don't perform server changes when event was triggered from code
if (wasTriggeredByCode) {
return;
}
// Send request.
var move = $.parseJSON($.ajax({
url: "./jstree.php",
type: 'post',
async: false,
data: {
operation: "move_dir",
....
}
}).responseText);
if(move.success){
successes.push(node);
}
});
// Don't continue when event was triggered from code
if (wasTriggeredByCode) {
return;
}
// Roll back the tree here
jsTree.rollback(data.rlbk);
// Move the nodes
for (var i=0; i < successes.length; i++) {
var node = successes[i];
// According to the documentation this will trigger the move event,
// which will result in infinite recursion. To avoid this you'll need
// to set a flag or indicate that you're redoing the move.
node.data('redoing', true);
jsTree.move_node(node, ...);
// Remove the flag so that additional moves aren't ignored
node.removeData('redoing');
}
});
I thought about having something like "onbeforenodemove" event in jstree, something like this:
$("#tree").jstree({...}).bind("before_move_node.jstree", function (e, data) {...}
So I looked inside jstree.js file (version jsTree 3.1.1) and searched for declaration of original "move_node.jstree" handler. It found it declared starting line 3689:
move_node: function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {...}
This function contains the following line at the end of its body:
this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
The above line actually calls your callback declared using .bind("move_node.jstree").
So at the beginning of this function body, I added this:
var before_data = { "node": obj, "parent": new_par.id, "position": pos, "old_parent": old_par, "old_position": old_pos, 'is_multi': (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign': (!old_ins || !old_ins._id), 'old_instance': old_ins, 'new_instance': this, cancelled: false };
this.trigger('before_move_node', before_data);
if (before_data.cancelled) {
return false;
}
Mind "cancelled": false at the end of before_data assigned value.
Also mind inserting the above after new_par, etc. values are assigned.
Code (jsTree instantiation) on my page looks now like this:
$('#tree')
.jstree({
core: {...},
plugins: [...]
})
.bind('before_move_node.jstree', function (e, data) {
if (...) {
data.cancelled = true;
}
})
data object passed to 'before_move_node.jstree' contains the same values that you receive in standard 'move_node.jstree' data argument so you have everything to decide whether you want to cancel the move or let it go. If you decide to cancel, just set the additional 'cancelled' property to true. The entire move will then not happen.
As the documentation says https://github.com/vakata/jstree/wiki#more-on-configuration, you can check more.core property
Example
$('#jstree1').jstree({
core: {
check_callback: async (operation, node, node_parent, node_position, more) => {
switch (true) {
case operation === 'move_node':
let canmove = true
const dropped = more.core === true // not dragging anymore...
if (dropped) {
// before move..
const success = await yourHttpRequest()
if (!success) {
canmove = false
}
} else {
canmove = yourCheckHere()
}
return canmove
}
}
}
})
Example 2
document.addEventListener("DOMContentLoaded", function () {
const bootstrap = (() => {
myTree.mySetup()
})
const myTree = {
mySetup: () => {
$('#jstree1').jstree({
core: {
check_callback: (operation, node, node_parent, node_position, more) => {
switch (true) {
case operation === 'move_node':
return myTree.myGates.canMove(node, node_parent, node_position, more)
}
// deny by default
return false
}
},
plugins: ['dnd']
})
.on('move_node.jstree', (node, parent, position, old_parent, old_position, is_multi, old_instance, new_instance) => {
myTree.myHandlers.onMove({
node, parent, position, old_parent, old_position, is_multi, old_instance, new_instance
})
})
},
myGates: {
canMove: (node, node_parent, node_position, more) => {
const canmove = true
const dropped = more.core === true
if (dropped) {
const success = alberoSx.myHandlers.onBeforeMove({
node, node_parent, node_position, more
})
if (!success) {
canmove = false
}
} else {
canmove = yourCheckHere()
}
return canmove
}
},
myHandlers: {
onBeforeMove: async () => {
// try to update the node in database
const success = await yourHttpRequestHere()
return success
},
onMove: () => {
// node moved in the ui
// do other stuff...
},
}
}
bootstrap()
})

Categories