I have a list linked to a store filled with Facebook friends. It contains around 350 records.
There is a searchfield at the top of the list which triggers the following function on keyup:
filterList: function (value) {
// console.time(value);
if (value === null) return;
var searchRegExp = new RegExp(value, 'g' + 'i'),
all = Ext.getStore('Friends'),
recent = Ext.getStore('Recent'),
myFilter;
all.clearFilter();
recent.clearFilter();
// console.log(value, searchRegExp);
myFilter = function (record) {
return searchRegExp.test(record.get('name'));
}
all.filter(myFilter);
recent.filter(myFilter);
// console.timeEnd(value);
},
Now, this used to work fine with ST2.1.1 but since I upgraded the app to ST2.2. It's really slow. It even makes Safari freeze and crash on iOS...
This is what the logs give :
t /t/gi Friends.js:147
t: 457ms Friends.js:155
ti /ti/gi Friends.js:147
ti: 6329ms Friends.js:155
tit /tit/gi Friends.js:147
tit: 7389ms Friends.js:155
tito /tito/gi Friends.js:147
tito: 7137ms
Does anyone know why it behaves like this now, or does anyone have a better filter method.
Update
Calling clearFilter with a true paramater seems to speed up things, but it's not as fast as before yet.
Update
It actually has nothing to do with filtering the store.
It has to do with rendering list-items. Sencha apparently create a list-item for every record I have in the store instead of just creating a couple of list-items and reusing them
Could there be an obvious reason it's behaving this way ?
Do you have the "infinite" config on your list set to true?
http://docs.sencha.com/touch/2.2.0/#!/api/Ext.dataview.List-cfg-infinite
You probably don't want the list rendering 300+ rows at once, so setting that flag will reduce the amount of DOM that gets generated.
Related
I am going to do live data streaming on ag-grid datatable, so I used DeltaRowData for gridOptions and added getRowNodeId method as well which return unique value 'id'.
After all, I got a live update result on my grid table within some period I set, but some rows are duplicated so I can notice total count is a bit increased each time it loads updated data. The question title is warning message from browser console, I got bunch of these messages with different id number. Actually it is supposed not to do this from below docs. This is supposed to detect dups and smartly added new ones if not exist. Ofc, there are several ways to get refreshed data live, but I chose this one, since it says it helps to persist grid info like selected rows, current position of scroll on the grid etc. I am using vanilla js, not going to use any frameworks.
How do I make live data updated periodically without changing any current grid stuff? There is no error on the code, so do not try to speak about any bug. Maybe I am wrong with current implementation, Anyway, I want to know the idea or hear any implementation experience on this.
let gridOptions = {
....
deltaRowDataMode: true,
getRowNodeId = (data) => {
return data.id; // return the property you want set as the id.
}
}
fetch(loadUrl).then((res) => {
return res.json()
}).then((data) => {
gridOptions.api.setRowData(data);
})
...
If you get:
duplicated node warning
it means your getRowNodeId() has 1 value for 2 different rows.
here is part from source:
if (this.allNodesMap[node.id]) {
console.warn("ag-grid: duplicate node id '" + node.id + "' detected from getRowNodeId callback, this could cause issues in your grid.");
}
so try to check your data again.
if u 100% sure there is an error not related with your data - cut oof the private data, create a plinkr/stackblitz examples to reproduce your issue and then it would be simpler to check and help you.
My question comes from the selected answer here
<ui-select-choices repeat="hero in getSuperheroes($select.search) | filter: $select.search">
<div ng-bind="hero"></div>
works perfectly fine with:
$scope.getSuperheroes = function(search) {
var newSupes = $scope.superheroes.slice();
if (search && newSupes.indexOf(search) === -1) {
newSupes.unshift(search);
}
return newSupes;
}
However, when I put a breakpoint on $scope.superheroes I see its called as many times as my data. Data is very large. Its fetched from a $http.get() request and I'm handling not loading the data in the textbox all at once.
However, I don't want to call this until I actually start typing anything in the textbox. I tried to call getSuperheroes using
on-select="getSuperheroes($select.search)"
and similarly
refresh="getSuperheroes($select.search)"
but they do not help allowing manual entry of data.
What do I use to call the function and yet get the task done just like how it works in the referenced answer?
I recommend you to do this:
In order to improve performance get rid of repeat="hero in getSuperheroes($select.search) | filter: $select.search", since if your data doesn't change the function getSuperheroes will be triggered (anyway) every time a $digest is executed (no matter the var newSupes changes or not in getSuperheroes) (that's wahat's happening right now!). Instead put (for instance) repeat="hero in myHeroes" and then in your getSuperheroes func do this:
$scope.getSuperheroes = function(search) {
var aux = $scope.superheroes.slice();
if (search && aux.indexOf(search) === -1) {
aux.unshift(search);
}
$scope.myHeroes = aux;
}
Keep the refresh="getSuperheroes($select.search)", this will search the new values by calling getSuperheroes which will update the $scope.myHeroes var and then it will be refreshed in the view.
Add the property refresh-delay="500" (500 is just an example, put whatever fits your needs). This will delay the search that amount of milisecond. This is useful when you do not want start searching immediately. For instance if the user wants to search "Superman" and she/he type "Sup" in less than 500 milisec the search will start with the string "Sup". If you do not set that property the search would be executed 3 times (one for "S" another for "Su" and a third one for "Sup")
PS:
-- If you want to get the ui-select with some values initially, You must call the getSuperheroes func on your controller start.
-- You may need to declare $scope.myHeroes = []; at the beginning of your controller (it depends on your controller implementation).
-- You might want to read more info about $digest on the AngularJS official doc, on SO there are some related posts:
How exactly does the AngularJS Digest Loop work?
Why do i have to call $scope.$digest() here?
Angular $scope.$digest vs $scope.$apply
$apply vs $digest in directive testing
I have two jQuery mobile pages (#list and #show). There are several items on the #list page with different IDs. If I click on item no.5, the ID no5 will be stored in localStorage and I will be redirected to page #show
Now the problem:
Storing the ID in localStorage works, but the next page shows me not the item no.5, but it shows me an old item, that was in the localStorage before.
script from page #list
localStorage.setItem("garageID", $(this).attr('id'));
window.location.replace("#show");
I encountered this problem too (and not on a mobile : on Chromium/linux).
As there doesn't seem to be a callback based API, I "fixed" it with a timeout which "prevents" the page to be closed before the setItem action is done :
localStorage.setItem(name, value);
setTimeout(function(){
// change location
}, 50);
A timeout of 0 might be enough but as I didn't find any specification (it's probably in the realm of bugs) and the problem isn't consistently reproduced I didn't take any chance. If you want you might test in a loop :
function setLocalStorageAndLeave(name, value, newLocation){
value = value.toString(); // to prevent infinite loops
localStorage.setItem(name, value);
(function one(){
if (localStorage.getItem(name) === value) {
window.location = newLocation;
} else {
setTimeout(one, 30);
}
})();
}
But I don't see how the fact that localStorage.getItem returns the right value would guarantee it's really written in a permanent way as there's no specification of the interruptable behavior, I don't know if the following part of the spec can be legitimately interpreted as meaning the browser is allowed to forget about dumping on disk when it leaves the page :
This specification does not require that the above methods wait until
the data has been physically written to disk. Only consistency in what
different scripts accessing the same underlying list of key/value
pairs see is required.
In your precise case, a solution might be to simply scroll to the element with that given name to avoid changing page.
Note on the presumed bug :
I didn't find nor fill any bug report as I find it hard to reproduce. In the cases I observed on Chromium/linux it happened with the delete operation.
Disclaimer: This solution isn't official and only tested for demo, not for production.
You can pass data between pages using $.mobile.changePage("target", { data: "anything" });. However, it only works when target is a URL (aka single page model).
Nevertheless, you still can pass data between pages - even if you're using Multi-page model - but you need to retrieve it manually.
When page is changed, it goes through several stages, one of them is pagebeforechange. That event carries two objects event and data. The latter object holds all details related to the page you're moving from and the page you're going to.
Since $.mobile.changePage() would ignore passed parameters on Multi-page model, you need to push your own property into data.options object through $.mobile.changePage("#", { options }) and then retrieve it when pagebeforechange is triggered. This way you won't need localstorage nor will you need callbacks or setTimeout.
Step one:
Pass data upon changing page. Use a unique property in order not to conflict with jQM ones. I have used stuff.
/* jQM <= v1.3.2 */
$.mobile.changePage("#page", { stuff: "id-123" });
/* jQM >= v1.4.0 */
$.mobile.pageContainer.pagecontainer("change", "#page", { stuff: "id-123" });
Step two:
Retrieve data when pagebeforechange is triggered on the page you're moving to, in your case #show.
$(document).on("pagebeforechange", function (event, data) {
/* check if page to be shown is #show */
if (data.toPage[0].id == "show") {
/* retrieve .stuff from data.options object */
var stuff = data.options.stuff;
/* returns id-123 */
console.log(stuff);
}
});
Demo
just a very short question on using Backbone.js with LocalStorage:
I'm storing a list of things (Backbone collection) in LocalStorage. When my website is open in multiple browser windows / tabs and the user in both windows adds something to the list, one window's changes will overwrite the changes made in the other window.
If you want to try for yourself, just use the example Backbone.js Todo app:
Open http://backbonejs.org/examples/todos/index.html in two browser tabs
Add an item 'item1' in the first tab and 'item2' in the second tab
Refresh both tabs: 'item1' will disappear and you'll be left with 'item2' only
Any suggestions how to prevent this from happening, any standard way to deal with this?
Thxx
The issue is well-known concurrency lost updates problem, see Lost update in Concurrency control?.
Just for your understanding I might propose the following quick and dirty fix, file backbone-localstorage.js, Store.prototype.save:
save: function() {
// reread data right before writing
var store = localStorage.getItem(this.name);
var data = (store && JSON.parse(store)) || {};
// we may choose what is overwritten with what here
_.extend(this.data, data);
localStorage.setItem(this.name, JSON.stringify(this.data));
}
For the latest Github version of Backbone localStorage, I think this should look like this:
save: function() {
var store = this.localStorage().getItem(this.name);
var records = (store && store.split(",")) || [];
var all = _.union(records, this.records);
this.localStorage().setItem(this.name, all.join(","));
}
You may want to use sessionStorage instead.
See http://en.wikipedia.org/wiki/Web_storage#Local_and_session_storage.
Yaroslav's comment about checking for changes before persisting new ones is one solution but my suggestion would be different. Remember that localStorage is capable of firing events when it performs actions that change the data it holds. Bind to those events and have each tab listen for those changes and view re-render after it happens.
Then, when I make deletions or additions in one tab and move over to the next, it will get an event and change to reflect what happened in the other tab. There won't be weird discrepancies in what I'm seeing tab to tab.
You will want to give some thought to making sure that I don't lose something I was in the middle of adding (say I start typing a new entry for my to-do list), switch to another tab and delete something, and then come back I want to see the entry disappear but my partially typed new item should still be available for me.
NOTICE: THIS IS SOLVED, I WILL PUBLISH THE SOLUTION HERE ASAP.
Hey all,
Ok... I have a simple dojo page with the bare essentials. Three UL's with some LI's in them. The idea si to allow drag-n-drop among them but if any UL goes empty due to the last item being dragged out, I will put up a message to the user to gie them some instructions.
In order to do that, I wanted to extend the dojo.dnd.Source dijit and add some intelligence. It seemed easy enough. To keep things simple (I am loading Dojo from a CDN) I am simply declating my extension as opposed to doing full on module load. The declaration function is here...
function declare_mockupSmartDndUl(){
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
markupFactory: function(params, node){
//params._skipStartup = true;
return new mockup.SmartDndUl(node, params);
},
onDndDrop: function(source, nodes, copy){
console.debug('onDndDrop!');
if(this == source){
// reordering items
console.debug('moving items from us');
// DO SOMETHING HERE
}else{
// moving items to us
console.debug('moving items to us');
// DO SOMETHING HERE
}
console.debug('this = ' + this );
console.debug('source = ' + source );
console.debug('nodes = ' + nodes);
console.debug('copy = ' + copy);
return dojo.dnd.Source.prototype.onDndDrop.call(this, source, nodes, copy);
}
});
}
I have a init function to use this to decorate the lists...
dojo.addOnLoad(function(){
declare_mockupSmartDndUl();
if(dojo.byId('list1')){
//new mockup.SmartDndUl(dojo.byId('list1'));
new dojo.dnd.Source(dojo.byId('list1'));
}
if(dojo.byId('list2')){
new mockup.SmartDndUl(dojo.byId('list2'));
//new dojo.dnd.Source(dojo.byId('list2'));
}
if(dojo.byId('list3')){
new mockup.SmartDndUl(dojo.byId('list3'));
//new dojo.dnd.Source(dojo.byId('list3'));
}
});
It is fine as far as it goes, you will notice I left "list1" as a standard dojo dnd source for testing.
The problem is this - list1 will happily accept items from lists 2 & 3 who will move or copy as apprriate. However lists 2 & 3 refuce to accept items from list1. It is as if the DND operation is being cancelled, but the debugger does show the dojo.dnd.Source.prototype.onDndDrop.call happening, and the paramaters do look ok to me.
Now, the documentation here is really weak, so the example I took some of this from may be way out of date (I am using 1.4).
Can anyone fill me in on what might be the issue with my extension dijit?
Thanks!
If you use Dojo XD loader (used with CDNs), all dojo.require() are asynchronous. Yet declare_mockupSmartDndUl() assumes that as soon as it requires dojo.dnd.Source it is available. Generally it is not guaranteed.
Another nitpicking: dojo.dnd.Source is not a widget/dijit, while it is scriptable and can be used with the Dojo Markup, it doesn't implement any Dijit's interfaces.
Now the problem — the method you are overriding has following definition in 1.4:
onDndDrop: function(source, nodes, copy, target){
// summary:
// topic event processor for /dnd/drop, called to finish the DnD operation
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
// target: Object
// the target which accepts items
if(this == target){
// this one is for us => move nodes!
this.onDrop(source, nodes, copy);
}
this.onDndCancel();
},
Notice that it has 4 arguments, not 3. As you can see if you do not pass the 4th argument, onDrop is never going to be called by the parent method.
Fix these two problems and most probably you'll get what you want.
In the end, I hit the Dojo IRC (great folks!) and we ended up (so far) with this...
function declare_mockupSmartDndUl(){
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
markupFactory: function(params, node){
//params._skipStartup = true;
return new mockup.SmartDndUl(node, params);
},
onDropExternal: function(source, nodes, copy){
console.debug('onDropExternal called...');
// dojo.destroy(this.getAllNodes().query(".dndInstructions"));
this.inherited(arguments);
var x = source.getAllNodes().length;
if( x == 0 ){
newnode = document.createElement('li');
newnode.innerHTML = "You can drag stuff here!";
dojo.addClass(newnode,"dndInstructions");
source.node.appendChild(newnode);
}
return true;
// return dojo.dnd.Source.prototype.onDropExternal.call(this, source, nodes, copy);
}
});
}
And you can see where I am heading, I put in a message when the source is empty (client specs, ug!) and I need to find a way to kill it when something gets dragged in (since it is not, by definition, empty any more ona incomming drag!). That part isnt workign so well.
Anyway, the magic was not to use the onDnd_____ functions, but the higher level one and then call this.inherited(arguments) to fire off the built in functionality.
Thanks!
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
Dojo require statement and declare statement are next to next. I think that will cause dependencies problem.
the dojo require statement should go outside onload block and the declare statement should be in onload block.