I was thinking on rewriting our legacy app which is using pure jQuery. It renders log data which it gets by websocket, it shows only last 100 records by removing old ones and appending new ones.
Because rendering speed matters, I've tried first to render random incoming data and Ractive's twice slower than our jQuery code. By benchmarks jQuery renders 1000 records in 15 seconds and Ractive's version in 30 seconds. ( our backend code pushes each event with 0.01 s delay )
Thus I wonder is there any tweak settings? The code I use is simple:
var LogApp = Ractive.extend({
template: '#items',
init: function() {
var self = this;
socket.bind("logs", function(data_raw) {
var data = JSON.parse(data_raw);
if (self.data.items.length > 100) {
self.pop('items');
}
self.unshift('items', data);
});
}
});
var ractive = new LogApp({
el: react,
data: {
items: []
}
});
<script id='items' type='text/ractive'>
{{#each items:i}} {{>item}} {{/each}}
</script>
<script id='item' type='text/ractive'>
<tr>
<td>{{id}}</td>
<td>{{log_level}}</td>
<td>{{log_message}}</td>
</tr>
</script>
With Ractive 0.7, performance is now better. It performs at ~11 seconds, with each item being about 10ms (See http://jsfiddle.net/aqe53ocm/).
You can also try using a merge instead of two operations, pop and unshift:
var copy = self.get('items').slice();
if (copy.length > 100) {
copy.pop();
}
copy.unshift(data);
self.merge('items', copy);
See http://jsfiddle.net/56hfm4bt/.
For example and timings having the dev tools open will impact times because it's console.time logging each item, so try without.
For the curious, there are changes coming in 0.8 that will spead this up to ~1ms per item.
Related
Basically I do have a datatable where I render rows with knockout 'foreach'.
Initial render will display 100 rows and there is a button which will request next 100 rows from the server via ajax call on click.
Because the table is quite large(many columns) with many observable it takes long time to render.
First 100 rows are still okay, but if I want to show more it takes really long time especially when it reaches e.g 1000 rows.
Tired to figure out why this is happening and I found out when I add new rows in observalbeArray which contains all the rows, the UI renders all the rows starting with first one all the time. My expectation was to see that the grid is updated with only the new added rows.
dataSource it is a pureComputed
UPDATE: this is how I add next 100 rows
result.data.TotalResults = result.data.DataRows.length + viewmodel.dataSource().length;
viewmodel.ds.pushAll(result.data.DataRows);
if you want ko to only render new rows, you should keep the original observableArray and only use push to add new rows.
below is the example how to use
const vm = {
data: ko.observableArray([])
};
ko.applyBindings(vm);
// reference of observable data
var _data = vm.data();
setTimeout(() => {
const ajaxData = ['one', 'two', 'three'];
_data.push(...ajaxData);
vm.data.valueHasMutated();
}, 100);
setTimeout(() => {
// midify already rendered <li> to see if it gets re-rendered by ko
const firstLi = document.querySelector('ul > li');
firstLi.innerText = 'modified';
firstLi.style.color = 'red';
}, 1000);
setTimeout(() => {
var ajaxData2 = ['four', 'five'];
_data.push(...ajaxData2);
vm.data.valueHasMutated();
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: data">
<li data-bind="text: $data">
</ul>
I am experiencing this weird scenario that I am unable to figure out what the problem is. There is a pagination for a collection which works fine when navigating. I have 5 documents in a collection with each to display per 2 on a page sing the pagination. Each document has a url link that when clicked it displays the full page for the document.
The challenge now is that if I click a document on the first page, it displays the full record, but if I navigate to the next page and click a document, it displays a blank page. I have tried all I could but haven't gotten what is to be made right.
These earlier posts are a build up to this present one: Publish and subscribe to a single object Meteor js, Meteor js custom pagination.
This is the helper
singleSchool: function () {
if (Meteor.userId()) {
let myslug = FlowRouter.getParam('myslug');
var subValues = Meteor.subscribe('SingleSchool', myslug );
if (myslug ) {
let Schools = SchoolDb.findOne({slug: myslug});
if (Schools && subValues.ready()) {
return Schools;
}
}
}
},
This is the blaze template
<template name="view">
{{#if currentUser}}
{{#if Template.subscriptionsReady }}
{{#with singleSchool}}
{{singleSchool._id}}
{{singleSchool.addschoolname}}
{{/with}}
{{/if}}
{{/if}}
</template>
try this;
onCreated function:
Template.view.onCreated(function(){
this.dynamicSlug = new ReactiveVar("");
this.autorun(()=>{
// When `myslug` changes, subscription will change dynamically.
this.dynamicSlug.set(FlowRouter.getParam('myslug'));
Meteor.subcribe('SingleSchool', this.dynamicSlug.get());
});
});
Helper
Template.view.helpers({
singleSchool(){
if (Meteor.userId()) {
let school = SchoolDb.findOne({slug: Template.instance().dynamicSlug.get()});
if (school) {
return school;
}
}
}
});
I have page wich displays time-dependent text-snippets from a json with Ractive.js.
After initialisation, I want to check every minute, if the stop-time, which is stored in the json, is due. Then the text-snippet should be hidden.
Things I've tried:
{{#if time_due > Date.time()}}
do whatever
{{/if}}
The above doesn't work for me.
I also played around with computed properties of Ractive.js, but that didn't work either.
Has somebody a good idea and/or can help me?
Ractive won't recompute expressions (like time_due > Date.now()) unless it thinks something has changed. The easy solution is to assign the current time to a data property and update it periodically:
var ractive = new Ractive({
el: 'main',
template: '#template',
data: {
timeNow: Date.now(),
displayUntil: Date.now() + 5000
}
});
setInterval( function () {
ractive.set( 'timeNow', Date.now() );
}, 500 );
<script src='http://cdn.ractivejs.org/edge/ractive.js'></script>
<main></main>
<script id='template' tyle='text/html'>
{{#if timeNow < displayUntil}}
<p>now you see me</p>
{{else}}
<p>now you don't</p>
{{/if}}
<p>(time remaining: {{displayUntil - timeNow}}ms)</p>
</script>
I have an angular application where I have a timeline with list event dates and the respective event description. This is the Html source code.
<!-- timeline -->
<h4 class="font-thin m-t-lg m-b-lg text-primary-lt">Historical Timeline</h4>
<p></p>
<div id="timeline"class="timeline m-l-sm m-r-sm b-info b-l">
<div ng-repeat = "timeline in formattedTimelineData | orderBy : '-eventDate'">
<div class = "tl-item">
<i class="pull-left timeline-badge {{timeline.class}} "></i>
<div class="m-l-lg">
<div id="eventDate{{$index}}" class="timeline-title">{{timeline.eventDate}}</div>
<p id="eventDescription{{$index}}" class="timeline-body">{{timeline.description}}</p>
</div>
</div>
</div>
</div>
<!-- / timeline -->
Now I am basically trying to make use protractor to ensure that the correct event date matches the event description. So i decided to use a map function. The issue is I would have a variable x which would tell me how many events there are . For example there can be 2 events, 6 events, etc. Events are dynamically
generated dynamically as you can tell by looking at html code also. Here is the code for my test I wrote.
it('FOO TEST', function(){
var x = 0;
while(x<4){
var timeline = element.all(by.css('#timeline')).map(function (timeline) {
return {
date: timeline.element(by.css('#eventDate'+x)).getText(),
events: timeline.element(by.css('#eventDescription'+x)).getText()
}
});
x++
}
timeline.then(function (Value) {
console.log(Value);
});
});
The issue is that for some reason in command line it only prints the last event out of 5 events. It does not print other events. I am definitely doing something wrong. I am brand new to promises so any suggestion here is appreciated. And yes i want to do like a individual test for each event in the timeline.
The problem is in the timeline locator: #timeline matches the timeline container while you need the inner repetative timeline blocks. Here is how you can match them:
var timeline = element.all(by.repeater('timeline in formattedTimelineData')).map(function (timeline) {
return {
date: timeline.element(by.binding('timeline.eventDate')).getText(),
events: timeline.element(by.binding('timeline.description')).getText()
}
});
timeline.then(function (timeline) {
console.log(timeline);
});
You can then loop over items like this:
timeline.then(function (timeline) {
for (var i = 0; i < timeline.length; ++i) {
// do smth with timeline[i]
}
});
Or, you can assert the complete timeline variable which is a promise and can be implicitly resolved by expect into an array of objects, for instance:
expect(timeline).toEqual([
{
date: "First date",
events: "Nothing happened"
},
{
date: "Second date",
events: "First base"
},
{
date: "Third date",
events: "Second base"
},
]);
I recommend against putting logic in your test - http://googletesting.blogspot.com/2014/07/testing-on-toilet-dont-put-logic-in.html.
A while loop is logic.
You should know ahead of time how many events will be in your timeline. In the example case 4. Then your specs should look like
element.all(by.css("#timeline")).then(function(events){
expect(events.count).toEqual(4);
expect(events[0].someThing()).toEqual(expectedValue0);
expect(events[1].someThing()).toEqual(expectedValue1);
...
expect(events[3].someThing()).toEqual(expectedValue3);
})
The repeater in my case has a lot of elements. I dont want to repeat through all elementsas my protractor spec times out while looping through so many elements. How can I just restrict the loop to run only for 1st 10 elements of the repeater? I have tried many things but I am just not able to get this working. How to loop only through first 10 elements of a repeater when using map()
The timeline variable in above example returns data for all elements in the repeater. How can i get the timeline variable to just have data for first 10 elements of the repeater as the looping through 1000 of entries of the repeater is time consuming that causes my protractor spec to timeout.
I've checked this post, and this one, and this one , and numerous others and none of the solutions seem to help me at all. All I'm trying to do is replace the contents of a view with an array of html. Each element in the array is created by using the underscore templating engine. Here's my code:
The Template:
<script type="text/template" id="radioItemTemplate">
<li>
<div class="button price <% if(enabled){%>enabled<%}%>">$<%=price%></div>
<div class="title name"><%=name%></div>
</li>
</script>
The Javascript:
iHateThisView = Backbone.View.extend({
template: _.template($("#radioItemTemplate").html()),
events:{
"click .price": "_onRadioItemClick"
},
radioItems: null,
radioItem: null,
initialize: function (options) {
this.radioItems = options.radioItems;
this.radioItem = options.radioItem;
},
render: function () {
trace("rendering");
var radioItems = this.radioItems.first(3);
var activeRadioItem = this.radioItem.get('name');
var result = [];
var scope = this;
_.forEach(radioItems, function (radioItem) {
var option = {
name: radioItem.get('name'),
price: radioItem.get('price'),
enabled: activeRadioItem == radioItem.get('name')
};
result.push(scope.template(option));
});
//THE TRICKY ZONE -START
this.$el.html(result);
//THE TRICKY ZONE -END
return this;
},
_onRadioItemClick: function (event) {
$el = this.$el;
var clickedName = $el.find('price');
console.log('clickedName');
}
});
Aside from it wrapping my html with a <div> this does exactly what I want on the first render. However if I called my render function again, none of the events work. So based on all my readings, I figured this.delegateEvents() should fix the loss of events, so I tried this:
//THE TRICKY ZONE -START
this.$el.html(result);
this.delegateEvents();
//THE TRICKY ZONE -END
Which from what I can tell did nothing. On the first render when I click on the radioItems I'd get my console.log, but again not after a re-render
so then I read that I might have to do this:
//THE TRICKY ZONE -START
this.$el.html(result);
this.delegateEvents(this.events);
//THE TRICKY ZONE -END
Which also did nothing.
So then I tried a different method:
//THE TRICKY ZONE -START
this.setElement(result);
this.delegateEvents(); //with and without this line
//THE TRICKY ZONE -END
This added only the first item in the array, and the events didn't work even on the first render.
Please restore my sanity guys, I don't what else to do.