Sorting whole fancytree by title fails - dynatree

I have a js code at my page that gets JSON-hierarchy from server with separate load and builds a visual Fancytree hierarchy at page (i left html page parts out from example).
$(function() {
$("#tree").fancytree({
source: {
url: '/products/ftree/?format=json',
cache: false,
datatype: 'json',
},
checkbox: false,
})
sortProducts();
console.log('sorted');
});
function sortProducts(){
console.log('in sortProducts()');
var tree = $("#tree").fancytree("getTree");
tree.rootNode.sortChildren(null, true);
tree.visit( function(node) {
node.sortChildren(null, false);
return true;
});
tree.render(true, true);
console.log('end of sort func');
}
.
.
.
<div id='tree' ></div>
each node has titles like
1 Fooo
2 Bar
3 FoooBar
3-1 BarFooo
3-1-1 foofoofoo
3-1-2 barbarbar
4 Buuu
4-1 BuuuBooo
4-1-1 bubobubub
and so on. Page loads fine, it renders the tree correctly and mostly the nodes are in correct order, but at deeper parts 4-2 comes before 4-1 and so on. So I tried to sort them with .sortChildren(null, true) and whatever, but it doesn't sort them. I got the code into shape that it doesn't give any errors and prints debugs just fine, but tree doesn't sort correctly.
Note that code snippet has two attempts, trough rootNode and node traverse, I've tried both alone and together and get the same partially unsorted tree. In my understanding, sortChildren() should sort title-strings without overriding the default cmp function.
Any idea what's wrong with that?

This code is ok
var node = $("#tree").fancytree("getRootNode");
node.sortChildren(null, true);
but the problem is that you try to sort an empty tree, because the asynchronous request has not yet returned.
One solution would be to call it in the init event:
$("#tree").fancytree({
...
init: function(event, data) {
data.tree.getRootNode().sortChildren(null, true);
},
...

Related

vue.js how to show progress if processing data

I have a page build in vue.js where an API is called (using axios) getting a large json object (1k+).
While I am processing this object, I want to show user the progress of the operation - nothing fancy like progress bars, just a simple "Processed X from Y".
I have found example on how to do this in jquery of pure JS, but I can get this to work in vue.
Any help is appreciated.
Here is my skeleton code:
<div>Processed {{processed.current}} of {{processed.total}} records</div>
<script>
data() {
return {
progress:{
current:0,
total: 0
},
records: [],
};
},
mounted() {
this.getRecords();
},
methods: {
getRecords(){
axios({
method: "GET",
url: process.env.VUE_APP_REPORTING_API + "/Reports/orders",
headers: {
"content-type": "application/json",
Authorization: this.$cookie.get("wwa_token")
}
}).then(
result => {
this.progress.total = result.data.length;
//and here where the loop should happen, something like this
//obviously the below won't work :)
result.data.forEach(function(item) {
this.records.push(item);
this.progress.current++;
}
},
error => {
}
);
}
}
</script>
Well, the obvious problem with the posted code is that it has a syntax error (missing a closing parenthesis) and it establishes a new context. The code would (sort of) "work" if it was changed it to use arrow functions:
result.data.forEach(item => {
this.records.push(item);
this.progress.current++;
});
However, I don't think that's going to do what you want. The JavaScript code will process all the items before the user interface updates, so all the user would see would be "Processed N of N records". Even if you inserted a this.$forceUpdate() in the loop to make the interface update at each iteration, the changes would still be too fast for any user to see.
The real problem is that "processing" all the items only takes a few milliseconds. So it's always going to happen too quickly to show intermediate results.
If you're trying to show the progress of the AJAX request/response, that's an entirely different question which will require coordination between the client and the server. Search for HTTP chunked responses for a start.

Kendo UI autocomplete dynamically loading dBdata when typing

I am writing a kendo UI autocomplete widget. The requirement is EACH TIME when I type a letter after "minLength", the dataSource need to be dynamically loaded from dB EVERYTIME. One problem is that, when the dataSource load successfully in the first time, it stops loading data.
The code snippet is:
var data;
function getDataFromDb(){
// some code to grab dummyData from dB ...
return dummyData;
}
$("#someInputText").kendoAutoComplete({
minLength: 2,
dataTextField: "someField",
dataSource: getDataFromDb(),
filter: "startswith"
});
Thanks a lot.
More details on the post. In my situation, I don't use the readOption. The data comes from another ajax call like:
var data [];
//fire this ajax call when input string length comes to 4...
$.ajax({url: "some working url", success: function(result){
var data = result;
startKendoAutoComplete();
}
});
function startKendoAutoComplete(){
if( !$.isEmptyObject(data)) // set a breakPoint, have data
{
$("#inputText").kendoAutoComplete({
minLength: 4,
dataSource : data,
...
});
}
}
Also, the ajax call will be fired when the input string length comes to 4. However, the KendoAutoComplete doesn't start working....
Thanks a lot for your sugesstion.
If you init your dataSource with an array of object, your widget will work with this array only.
The first thing you'll have to create an dataSource object and set the serverFiltering property to true. Then, if you don't specify an url where the data will be fetched, you set you own transport.read function and from there you'll be able to implement your own logic. The read function will receive the readOption which will include all the relevant information to query tour data (top / skip / filter / sort ...). The readOptions will also provide a success function that should be used to return the value:
dataSource: {
serverFiltering: true,
transport: {
read: function (readOptions) {
readOptions.success(getDataFromDb(readOptions));
}
}
},

id already registered dojo

I have a TabContainer, to which I m adding ContentPanels. My requirement demands that, I reload this TabContainer(which different content in ContentPanels) everytime, I click a new row on grid(generated from ajax in the same page).
Initially, when I got the problem that id is already registered, I used destroyRecursive, as seen in suggested in one of the answers here.
Now, after using that, I m getting the following result:
Result after I click on any of the row, the first time:
Just like the way, I want, with the container and the 3 content panes.
Result after I click on any of the row, the second time, and any other times:
A new container with 3 content panes is placed on top of the old one with 3 content panes.
No matter, how many rows I click, the result always has 2 containers, with new one placed above the old one.
Below is the code, I have used.
<div id = "tabsContainer">
<div id="tabPanels" data-dojo-type = "dijit/layout/TabContainer"></div>
</div>
function getTabPanelsForTheRow() {
require(["dijit/layout/TabContainer",
"dijit/layout/ContentPane"], function (TabContainer, ContentPane) {
var tc = new TabContainer({}, "tabPanels");
var cp1 = new ContentPane({
title: "Contacts",
content: "These are the activities"
});
tc.addChild(cp1);
var cp2 = new ContentPane({
title: "Activities",
content: "These are the activities"
});
tc.addChild(cp2);
var cp3 = new ContentPane({
title: "Opportunities",
content: "We are known for our drinks."
});
tc.addChild(cp3);
tc.startup();
});
}
function destroyTabPanel() {
require(["dijit/layout/TabContainer"], function (TabContainer) {
var tp = dijit.byId("tabPanels");
tp.destroyRecursive(true);
});
}
Everytime, I click a row, I m calling destroyTabPanel() first, and then I m calling getTabPanelsForTheRow().
Looks to me like your problem is the "true" in your destroyRecursive() call which is instructing dojo to preserve the DOM. So you're destroying the widgets which has solved your duplicate id problem but the generated DOM is preserved; then when you call getTabPanelsForTheRow() it is generating 3 new panels over the existing ones.
What you want to do is, after calling destroyRecursive(), empty the container "tabPanel": domConstruct.empty("tabPanel") before calling getTabPanelsForTheRow().
On a side note, you're destroying and re-instantiating your panel widget everytime you click on a row, why not store a reference to your contentPanes within your TabContainer and then write a method which destroys the contentPanes only, empty the TabContainer node and then create the new panes...

Pass data-attribute value of clicked element to ajax settings

For an implementation of Magnific Popup, I need to pass a post id to the ajax settings. The post id is stored in a data attribute of the element to which Magnific Popup is bound. I would like this to work:
html element:
<a data-id="412">Clicke me</a>
Javascript:
$('.element a').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST',
data: ({
action:'theme_post_example',
id: postId
})
}
}
});
Where postId is read from the data attribute.
Thanks in advance.
$('.element a').magnificPopup({
callbacks: {
elementParse: function(item){
postData = {
action :'theme_post_example',
id : $(item.el[0]).attr('data-id')
}
var mp = $.magnificPopup.instance;
mp.st.ajax.settings.data = postData;
}
},
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST'
}
}
});
Here is how to do it:
html:
<a class="modal" data-id="412" data-action="theme_post_example">Click me</a>
jquery:
$('a.modal').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url : php_array.admin_ajax,
dataType : 'json'
}
},
callbacks: {
elementParse: function() {
this.st.ajax.settings.data = {
action : this.st.el.attr('data-action'),
id : this.st.el.attr('data-id')
}
}
},
parseAjax: function( response )
{
response.data = response.data.html;
}
});
php
function theme_post_example()
{
$id = isset( $_GET['id'] ) ? $_GET['id'] : false;
$html = '<div class="white-popup mfp-with-anim">';
/**
* generate your $html code here ...
*/
$html .= '</div>';
echo json_encode( array( "html" => $html ) );
die();
}
As this answer was the original question regarding inserting data into Magnific's ajax call, I'll post this here.
After many hours of trying to figure this out, you should know that if you're using a gallery with the ability to move between gallery items without closing the popup, using elementParse to set your AJAX data will fail when you visit an item after already viewing it (while the popup is still open).
This is because elementParse is wrapped up in a check that it makes detect if an item has already been 'parsed'. Here's a small explanation as to what happens:
Open gallery at item index 2.
Item has not been parsed yet, so it sets the parsed flag to true and runs the elementParse callback (in that order). Your callback sets the ajax options to fetch this item's data, all is well.
Move (right) to item index 3.
Same as above. The item has not been parsed, so it runs the callback. Your callback sets the data. It works.
Move (left) back to item index 2.
This time the item has been parsed. It skips re-parsing the item's element for assumed potential performance reasons.Your callback is not executed. Magnific's ajax data settings will remain the same as if it were item index 3.
The AJAX call is executed with the old settings, it returns with item index 3's data instead, which is rendered to the user. Magnific will believe it is on index 2, but it is rendering index 3's data.
To resolve this, you need to hook onto a callback which is always executed pre-ajax call, like beforeChange.
The main difference is that the current item isn't passed through into the callback. Fortunately, at this point, magnific has updated their pointers to the correct index. You need to fetch the current item's element by using:
var data = {}; // Your key-value data object for jQuery's $.ajax call.
// For non-closures, you can reference mfp's instance using
// $.magnificPopup.instance instead of 'this'.
// e.g.
// var mfp = $.magnificPopup.instance;
// var itemElement = mfp.items[mfp.index].el;
var itemElement = this.items[this.index].el;
// Set the ajax data settings directly.
if(typeof this.st.ajax.settings !== 'object') {
this.st.ajax.settings = {};
}
this.st.ajax.settings.data = data;
This answer can also be used as a suitable alternative to the currently highest voted, as it will work either way.
You may use open public method to open popup dynamically http://dimsemenov.com/plugins/magnific-popup/documentation.html#public_methods
postId = $(this).attr('data-id')
$(this) retrieve the current element (the link you clicked on), and attr the value of the specified attribute.

ExtJs 4 : Tree grid panel filter

I am using ExtJs 4 with a Tree panel on west region and TreeGrid panel on center region. Is there any way to filter the TreeGrid panel(center region) on selection of the treepanel(west) ??
I tried the following but no luck :
Ext.define('MyApp.view.MyViewport', {
extend: 'MyApp.view.ui.MyViewport',
initComponent: function() {
var me = this;
me.callParent(arguments);
me.down('#westTreePanel').getSelectionModel().on('selectionchange',me.CenterTreeFilter,me);
}, //end of initComponent
CenterTreeFilter: function(){
var selection = this.down('#westTreePanel').getView().getSelectionModel().getSelection()[0];
var centerTreeGrid = this.down('#centerTreeGrid');
console.log(selection.data.text);
centerTreeGrid.store.filterBy(function(rec, id){
console.log(rec);
return (rec.store("text") == selection.data.text);
});
console.log("sub store : " + this.down('#centerTreeGrid').getStore().storeId);
}
});
After days of fighting with this issue, I was finally able to get the functionality, albeit in a not so satisfying way. Also, only leaf nodes are currently hidden.
filtering all nodes that don't mention "text":
t.getRootNode().cascadeBy(function(n){
if (!n.hasChildNodes() &&
n.raw && n.raw.text.toLowerCase().indexOf(text.toLowerCase()) < 0) {
toRemove.push({ node: n, parent: n.parentNode });
}
});
To restore later, run:
function restoreTrees() {
for (var n in toRemove) {
toRemove[n].parent.appendChild(toRemove[n].node);
}
toRemove = [];
}
There are many flaws with this solution. Including that the restored tree will probably have a different ordering for their nodes. But hey, at least this is some progress.
Would love to see a better one! (Had it working beautifully in Ext JS 3, but now these darn nodes don't have a .ui.hide() function any more).
i've checked their example http://dev.sencha.com/deploy/ext-4.0.2a/examples/tree/treegrid.html, in fact the issue here is that the store for the treeGrid is a tree store which doesn;t have the method filterBy , the method filterBy is defined in ext.data.store and treeStore extends ext.data.abstractStore.. as i see it you have to apply your filters diferently, using the the filters config for the treeStore. You can define your filter and set the filterOnLoad on true and instead of calling the filterBy method do centerTreeGrid.store.fireEvent('load',selection). I hope this helps you
Edit
I haven't used filters for tree stores but i think you can do something like this
var treeFilter = new Ext.util.Filter({
filterFn: function(rec) {
console.log(rec);
return (rec.store("text") == selection.data.text);
});
And assign the filter to the treeStore in the initComponent
centerGrid.store.filters.add(treeFilter);
centerGrid.store.filterOnLoad = true;
And in the CenterTreeFilter function
centerGrid.store.fireEvent('load',selection);
P.s the code is untested and probably it will need some modifications, but i think this is the way to do it.