Meteor: Elements from CollectionA re-rendering when I insert to CollectionB - dom

I'm attempting to fade-in new elements in a reactive {{#each}} of the comments posted by users.
I have a code sample at https://gist.github.com/3119147 of a very simple comments section (textarea and new comment insert code not included, but it's very boilerplate.). Included is a snippet of CSS where I give .comment.fresh { opacity: 0; }, and then in my script, I have:
Template.individual_comment.postedago_str = function() {
var id = this._id;
Meteor.defer(function() {
$('#commentid_'+id+'.fresh').animate({'opacity':'1'}, function() {
$(this).removeClass('fresh');
});
});
return new Date(this.time).toString();
};
Which seems like a terrible place to execute an animation. My thinking is that each time a new comment is rendered, it will need to call all my Template.individual_comment.* functions, so that's why my animation defers from one of those. However, Meteor is calling Template.individual_comment.postedago_str() each time a different collection (Likes) is inserted to. This means I click the Like button, and my whole list of comments flashes white and fades back in (very annoying!).
I read the Meteor documentation and tried to figure out how to better slice up my templates so only chunks will update, and I added id="" attributes everywhere that seemed reasonable.. still this bug. Anyone know what's going on?
TIA!

As a workaround, you could wrap an {{if}} block around the fresh class on individual comments, that would check the comment's creation time and only add the fresh class in the first place if the comment is actually recent. Something like:
<div class="comment{{#if isActuallyFresh}} fresh{{/if}}" id="commentid_{{_id}}">
And then define the isActuallyFresh function:
Template.individual_comment.isActuallyFresh = function() {
if ((new Date().getTime() - this.time) < 300000) // less than 5 minutes old
return true;
else
return false;

Related

Protractor element handling

I have a question regarding how protractor handles the locating of elements.
I am using page-objects just like I did in Webdriver.
The big difference with Webdriver is that locating the element only happens when a function is called on that element.
When using page-objects, it is advised to instantiate your objects before your tests. But then I was wondering, if you instantiate your object and the page changes, what happens to the state of the elements?
I shall demonstrate with an example
it('Change service', function() {
servicePage.clickChangeService();
serviceForm.selectService(1);
serviceForm.save();
expect(servicePage.getService()).toMatch('\bNo service\b');
});
When debugging servicePage.getService() returns undefined.
Is this because serviceForm is another page and the state of servicePage has been changed?
This is my pageobject:
var servicePage = function() {
this.changeServiceLink = element(by.id('serviceLink'));
this.service = element(by.id('service'));
this.clickChangeService = function() {
this.changeServiceLink.click();
};
this.getService = function() {
return this.service.getAttribute('value');
};
};
module.exports = servicePage;
Thank you in advance.
Regards
Essentially, element() is an 'elementFinder' which doesn't do any work unless you call some action like getAttribute().
So you can think of element(by.id('service')) as a placeholder.
When you want to actually find the element and do some action, then you combine it like element(by.id('service')).getAttribute('value'), but this in itself isn't the value that you are looking for, it's a promise to get the value. You can read all about how to deal with promises elsewhere.
The other thing that protractor does specifically is to patch in a waitForAngular() when it applies an action so that it will wait for any outstanding http calls and timeouts before actually going out to find the element and apply the action. So when you call .getAttribute() it really looks like
return browser.waitForAngular().then(function() {
return element(by.id('service')).getAttribute('value');
});
So, in your example, if your angular pages aren't set up correctly or depending on the controls you are using, you might be trying to get the value before the page has settled with the new value in the element.
To debug your example you should be doing something like
it('Change service', function() {
servicePage.getService().then(function(originalService) {
console.log('originalService: ' + originalService);
});
servicePage.clickChangeService();
serviceForm.selectService(1);
serviceForm.save();
servicePage.getService().then(function(newService) {
console.log('newService: ' + newService);
});
expect(servicePage.getService()).toMatch('\bNo service\b');
});
The other thing that I'm seeing is that your pageObject appears to be a constructor when you could just use an object instead:
// name this file servicePage.js, and use as 'var servicePage = require('./servicePage.js');'
module.exports = {
changeServiceLink: element(by.id('serviceLink')),
service: element(by.id('service')),
clickChangeService: function() {
this.changeServiceLink.click();
},
getService: function() {
return this.service.getAttribute('value');
}
};
Otherwise you would have to do something like module.exports = new servicePage(); or instantiate it in your test file.
When you navigate another page, the web elements will be clear, that you selected. So you have to select again. You can select all elements that is in a page of HTML. You can click that you see. So the protactor + Selenium can decide what is displayed.
You have a mistake in your code, try this:
expect(servicePage.getService()).toMatch('\bNo service\b');

Blinking text on database changes [duplicate]

This question already has answers here:
Imitating a blink tag with CSS3 animations
(10 answers)
Closed 7 years ago.
I'm working on a small application that has a message window. The messages are stored in a db and updated by fetching 5 of the latest messages:
scienceTeamMessages = new Meteor.Collection('scienceTeamMessages');
Meteor.methods({
'sendMessageFromMS': function(message, destination) {
if (destination === "scienceTeam") {
scienceTeamMessages.insert({
message: message,
createdAt: new Date()
});
}
}
});
These messages are then iterated over in an html template:
{{#each messages}}
<li><h6>{{message}}</h6></li>
{{/each}}
What I would like, and I can't figure out how to do, is for the latest message to blink a few times, so as to draw attention from the user when a new message arrives. Like, fading in and out from black to red 3 times.
Any suggestions? I know how to do the css, but I am unsure about how to do it on changes to the database. That is why the other solutions on SO won't work in this specific question.
For the animations have a look at the link posted in the comments: Imitating a blink tag with CSS3 animations
If you'd like to add animations for a specific time limit use Meteor.setTimeout().
To do animations in Meteor see this Microscope demo example: https://github.com/DiscoverMeteor/Microscope/blob/master/client/templates/application/layout.js
And lastly, if you'd like to perform a certain action when an element is added to a collection, consider using cursor.observe or cursor.observeChanges which is documented here: http://docs.meteor.com/#/full/observe
A lot of links, but hopefully with all that together you can put together the solution you're looking for.
You need to set this logic on your helper :
Template.foo.helpers({
messages: function() {
var elements = scienceTeamMessages.find({},{sort: {created_at: -1}}).fetch();
for (var i = elements.length - 1; i >= 0; i--) {
elements[i].drawAttention = (i == 0);
};
return elements;
},
})
<template name="foo">
{{#each messages}}
<li><h6 {{#if drawAttention}}class="burn-yours-eyes"{{/if}}>{{message}}</h6></li>
{{/each}}
</template>
I did not tested this code, hope it will help you.

Storing appended elements to localStorage

I'm a teacher and creating a page to organize my lesson plans. There should be the ability to add new lessons (li) and new weeks (ul). The lessons are sortable between each of the weeks. Each newly added item will then be saved to localStorage.
So far, I'm able to create the lessons and new weeks. The sortable function works. The save function works... except that it will not save any of the new weeks (ul). When I refresh, the new lessons (li) are still on the page, but the new weeks (ul) are gone.
$("#saveAll").click(function(e) {
e.preventDefault();
var listContents = [];
$("ul").each(function(){
listContents.push(this.innerHTML);
})
localStorage.setItem('todoList', JSON.stringify(listContents));
});
$("#clearAll").click(function(e) {
e.preventDefault();
localStorage.clear();
location.reload();
});
loadToDo();
function loadToDo() {
if (localStorage.getItem('todoList')){
var listContents = JSON.parse(localStorage.getItem('todoList'));
$("ul").each(function(i){
this.innerHTML = listContents [i];
})
}
}
I created a fiddle here.
You can click the "Add New Week" button and then click the "Create Lesson" button and drag the new lesson into one of the weeks. After clicking "Save All", only the first week is saved.
I can't seem to figure out what's missing.
It's saving correctly, but since the page only has one <ul> element initially, that is the only one that gets populated in loadToDo(). (listContents has more than one element, but $("ul").each(...) only iterates over one element.)
There is a quick band-aid you can use to resolve this. Refactor your #new-week-but click handler into a named function:
function addNewWeek() {
var x = '<ul class="sortable weeklist"></ul>';
$(x).appendTo('.term').sortable({connectWith: '.sortable'});
}
$('#new-week-but').click(addNewWeek);
Then add this block after you fetch the array from storage but before you enumerate the <ul> elements:
var i;
for (i = 2; i < listContents.length; ++i) {
addNewWeek();
}
This will add the required number of <ul> elements before attempting to populate them.
I chose to initialize i to two because this creates two fewer than the number of elements in listContents. We need to subtract one because there is a <ul> in .term when the page loads, and another because the <ul id="new-lesson-list"> contents also get saved in listContents. (Consider filtering that element out in your #saveAll click handler.)
(Note that this requires merging all of your $(document).ready() functions into one big function so that addNewWeek() is visible to the rest of your code.)
Suggestions to improve code maintainability:
Give each editable <ul> a CSS class so that they can be distinguished from other random <ul> elements on the page. Filter for this class when saving data so that the "template" <ul> doesn't get saved, too.
Remove the one default editable <ul> from the page. Instead, in your loadToDo() function, add an else block to the if block and call addNewWeek() from the else block. Also, call it if listContents.length == 0. This will prevent duplicating the element in the HTML source (duplication is bad!) and having to account for it in your load logic.
If you implement both of these then you can initialize i to 0 instead of 2 in my sample code, which is a lot less weird-looking (and less likely to trip up future maintainers).

Isolate reactivity in an ordered list

I have got a template that shows tiles in a particular order:
<template name="container">
{{#each tiles}}{{>tile}}{{/each}}
</template>
Now the container is a list of tiles that is stored as an array in mongodb.
Since I want the tiles to be shown in the same order as they appear in that array, I'm using the following helper:
Template.container.tiles = function () {
return _.map(this.tiles || [], function(tileId) {
return _.extend({
container: this
}, Tiles.findOne({_id: tileId}));
}, this);
};
};
The problem is, that I:
Do not want the entire container to rerender when the any of it's contain tiles changes. (Only the relevent tile should be invalidated).
Do not want the entire container to rerender when a new tile is inserted. The rendered tile should be simply appended or insteted at the respective location.
Do not want the entire container to rerender when the order of the tiles is changed. Instead, when the order changes, the DOM objects that represent the tile should be rearranged without re-rendering the tile itself.
With the above approach I will not meet the requirements, because the each tiles data is marked as a dependency (when running Tiles.findOne({_id: tileId})) of the entire container and the entire array of tile-ids is part of the containers data and if that changes the entire container template is invalidated.
I'm thinking I should mark the cursor for the container as non-reactive. Something like this:
Containers.findOne({_id: containerId}, {reactive:false});
But I still need to find out when this container changes it's tiles array.
So something like
Deps.autorun(function() {
updateContainer(Containers.findOne({_id: containerId}));
});
But I want that container template to be highly reusable. So whatever solution there it should not require some preparations with dependencies.
Where do declare I run that autorun function? (surely i cannot do that in that helper, right?)
Is this the right approach?
Does anybody have better ideas on how to solve this problem?
Thanks in advance...
The way I usually approach this problem is by creating an auxiliary Collection object and populate it with a help of appropriate observer. In your case this might be something like:
// this one should be "global"
var tiles = new Meteor.Collection(null); // empty name
Now, depending on the current container, you can populate the tiles collection with corresponding data. Also, you'll probably need to remember each object's index:
Deps.autorun(function () {
var containerId = ... // get it somehow, e.g. from Session dictionary
var tilesIDs = Containers.findOne({_id:containerId}).tiles;
tiles.remove({}); // this will be performed any time
// the current container changes
Tiles.find({ _id : { $in : tilesIDs } }).observeChanges({
added: function (id, fields) {
tiles.insert(_.extend({
_index : _.indexOf(tilesIDs, id),
_id : id,
}, fields);
},
changed: ... // you'll also need to implement
removed: ... // these to guys
});
});
The helper code is now really simple:
Template.container.tiles = function () {
return tiles.find({}, {sort : {_index : 1}});
}
EDIT:
Please note, that in order to prevent the whole list being rerendered every time the container object changes (e.g. the order of tiles changes), you'll need to make a separate listOfTiles template that does not depend on the container object itself.

Event after DOM manipulation in Sencha Touch

Is their an event available after DOM manipulation in Sencha Touch has succeeded?
I want to measure the time it takes to render a list with 1000 elements.
Therefor, a timer is started when the list is initialized and stoppend when the list is painted like so:
listeners: {
initialize: function () {
start = new Date();
var store = Ext.getStore('Songs');
for (var i = 1; i <= 1000; i++) {
store.add({id: i});
}
},
painted: function () {
stop = new Date();
Ext.Msg("Timer", stop - start);
}
}
The painted event is triggerd before DOM manipulation so the 1000 listitems are not visible when the rentertime pops up.
Is there an other event that is triggerd after DOM has been manipulated and the list is updated?
Or is there an alternative method to measure the time it takes to do this?
Greets,
Sander Van Loock
Unfortunately you are not answering, therefore I can only guess:
You are adding each item seperately to the store. In Sencha Touch this will eat up time. Better to create an array of items and add them at once.
If you are adding items and the update of the store takes too long, stop sorting of the store.
If you are interested in the list you better work with before and after events. Something like before updatedata and after updatadata. Or you could add start to the first itemTpl.
But again. Painting of a list which is infinite will not take any real time. So the DOM part really is not what you are looking for.
If you are using the dataview, this might be different.