I have this code:
Template.messageList.onCreated(function onCreated() {
this.state = new ReactiveDict();
this.state.setDefault({
limit: 5
});
this.autorun(() => {
this.subscribe('messages', {limit: this.state.get('limit')});
});
});
Template.messageList.helpers({
messages () {
const instance = Template.instance();
return Messages.find({}, MessagesFilter.common({limit: instance.state.get('limit')}));
}
});
Template.messageList.events({
'click .ui.button.more' (event, instance) {
const limit = instance.state.get('limit');
instance.state.set('limit', limit + 5);
}
});
It's intended to be a infinite scroll, but it presents weird events. Right now, the elements on page are reloaded when the limit changes.
Looks like meteor is re-subscribing (which is expected by the documentation) but this is reloading all the documents, it deletes the messages and then reload the with the new limit value.
How can I prevent it? I want a infinite scroll, but the way it behaves right now looks impossible.
Here's a video of what's happening.
http://recordit.co/mlsGThOdHp
Here's the repository:
https://github.com/Sornii/sayito/tree/infinite-scroll
I changed
this.autorun(() => {
this.subscribe('messages', {limit: this.state.get('limit')});
});
To
Tracker.autorun(() => {
this.subscribe('messages', {limit: this.state.get('limit')});
});
And now it works the way I want to.
Related
I want to display a warning message to the user when the user’s connection is not optimal to process the current flow. But I can’t find a way to detect that information properly.
The solution I found is to calculate the loading time of each fragment to trigger or not the warning but this option impacts the performance of the application.
let localLowConnection = false;
localHlsInstance.on(Hls.Events.FRAG_LOADING, (event: any, data: any) => {
const id = setTimeout(() => {
if (!localLowConnection) {
isLowConnection(true);
localLowConnection = true;
}
}, 1000);
ids.push({ timerId: id, eventId: data.frag.sn });
});
localHlsInstance.on(Hls.Events.FRAG_LOADED, (event: any, data: any) => {
each(ids, (item, index) => {
if (item.eventId === data.frag.sn) {
clearTimeout(item.timerId);
}
});
if (localLowConnection) {
isLowConnection(false);
localLowConnection = false;
}
})
This code seems to work but it displays and removes the overlay almost instantly.
I find no post on this topic and nothing explicit in the hls.js documentation for this case. I also specify that the adapdative bitrate is not activated and I therefore have only one quality level.
Thank you for your help :)
I'm new to Cypress. My app as a "routing system" manually changes window.location.hash.
At some point, I click on a button that changes the hash and consequently should change the page during the test. I can see a "new url" entry appearing during the execution, but how can I make cypress visit that url?
In few words, what the problem is: you can see I type the password and then {enter}. Running the test I can see the hash change in the address bar, but the page doesn't change in accord to the hash change.
This is the testing code
context("Workflow", () => {
it("login", () => {
cy.visit("http://localhost:3000/src/#login")
cy.get("#username").type("demo").should("have.value", "demouser")
cy.get("#password").type("demo{enter}").should("have.value", "demo") // this should redirect to "/#home"
//cy.wait(10000)
cy.get(".subtitle").should("have.value", "Welcome") //this line fails as ".subtitle" is an element of "/#home"
})
})
EDIT: Following tons of failed attempts, I came up with a partially working, clunky and hacky solution. I think I shouldn't need to use reload() to solve that (there must be a better solution..), but to make it works I have to wait for all the remote requests to be done (otherwise reload() cancels them). I say partially working because you can see from the comments in the code if I try to visit #login first, then follow the redirect in #home and then change the page to #browser, the last one doesn't work (I can see the hash changing to #browser, but the page is still #home).
import 'cypress-wait-until';
let i = 0;
context("Workflow", () => {
it("login", () => {
cy.server( {
onRequest: () => {
i++;
},
onResponse: () => {
i--;
}
});
cy.visit("http://localhost:3000/src/#login")
cy.get("#username").type("demouser").should("have.value", "demouser")
cy.get("#password").type("demouser").should("have.value", "demouser")
cy.get("form#formLogin").submit()
cy.waitUntil(() => i > 0)
cy.waitUntil(() => i === 0)
cy.reload(); // it correctly change the hash AND the page to #home!
cy.url().should("include", "#home")
cy.get(".version").contains( "v2.0.0-beta") // it works!!
cy.get("a[data-id=browser]").click({force: true}) // it correctly changes the hash to #browser
cy.waitUntil(() => i > 0)
cy.waitUntil(() => i === 0)
cy.reload();
// the hash in the address bar is #browser, but the web page is #home
})
})
Cypress has an event called url:changed. See doc on Events here
Using this, something like this may work:
context("Workflow", () => {
it("login", () => {
cy.on('url:changed', url => {
cy.location('hash').then(hash => {
if (!url.endsWith(hash)) {
cy.visit(url);
}
});
});
cy.visit("http://localhost:3000/src/#login")
cy.get("#username").type("demo").should("have.value", "demouser")
cy.get("#password").type("demo{enter}").should("have.value", "demo") // this should redirect to "/#home"
cy.get(".subtitle").should("have.value", "Welcome") //this line fails as ".subtitle" is an element of "/#home"
})
})
Edit: just for troubleshooting purposes and to focus on your issue, can you try it like this without cy.location():
context("Workflow", () => {
it("login", () => {
cy.on('url:changed', url => {
cy.visit(url);
});
cy.visit("http://localhost:3000/src/#login")
cy.get("#username").type("demo").should("have.value", "demouser")
cy.get("#password").type("demo{enter}").should("have.value", "demo") // this should redirect to "/#home"
cy.get(".subtitle").should("have.value", "Welcome") //this line fails as ".subtitle" is an element of "/#home"
})
})
Edit: Have you tried cy.reload()
context("Workflow", () => {
it("login", () => {
cy.visit("http://localhost:3000/src/#login")
cy.get("#username").type("demo").should("have.value", "demouser")
cy.get("#password").type("demo{enter}").should("have.value", "demo") // this should redirect to "/#home"
cy.reload();
cy.get(".subtitle").should("have.value", "Welcome");
})
})
Thanks for all the attempts to solve, but they were all tricky workarounds to something as simple as "trigger the related listener when window.location.hash changes".
The root of the problem was a bug in Cypress. The bug on window.location.hash is present in 4.5.0, but it has been solved somewhere between 4.5.0 and 4.12.1.
In 4.12.1 the problem is solved.
There are some ways to do that. One of the basic approach is like this:
cy.visit(`your_login_page`)
cy.get('[data-testid="input-username"]').type(`demo`, { force: true })
cy.get('[data-testid="input-password"]')
.type(`demo`, {
force: true,
})
.wait(1000)
.then(() => {
cy.location().should((loc) => {
expect(loc.pathname).to.eq(your_new_url)
})
})
You should add data-testid or findByTestId to your elements with a unique id.
To visit the new URL, you can use cy.visit(loc.pathname)
I'm building a simple Vuejs website in which you can write notes about meetings. Upon loading it takes the meeting notes from the server and displays them. When the user then writes something he can click the "Save" button, which saves the text to the server. When the notes are saved to the server the Save-button needs to be disabled and display a text saying "Saved". When the user then starts writing text again it should enable the button again and display "Save" again. This is a pretty basic functionality I would say, but I'm having trouble with it.
Here's my textarea and my save button:
<textarea v-model="selectedMeeting.content" ref="meetingContent"></textarea>
<button v-on:click="saveMeeting" v-bind:disabled="meetingSaved">
{{ saveMeetingButton.saveText }}
</button>
In my Vue app I first initiate my data:
data: {
selectedMeeting: {},
meetings: [],
meetingSaved: true,
saveMeetingButton: {saveText: 'Save Meeting', savedText: 'Saved', disabled: true},
},
Upon creation I get the meeting notes from the server:
created() {
axios.get('/ajax/meetings')
.then(response => {
this.meetings = response.data;
this.selectedMeeting = this.meetings[0];
this.meetingSaved = true;
});
},
I've got a method to save the notes:
methods: {
saveMeeting: function () {
axios.post('/ajax/meetings/' + this.selectedMeeting.id, this.selectedMeeting)
.then(function (response) {
this.selectedMeeting = response.data;
console.log('Now setting meetingSaved to true');
this.meetingSaved = true;
console.log('Done setting meetingSaved to true');
});
},
},
And I've got a watcher in case something changes to the text which saves the text immediately (this saves with every letter I type, which I of course need to change, but this is just to get started.
watch: {
'selectedMeeting.content': function () {
this.meetingSaved = false;
console.log('Changed meeting ', new Date());
this.saveMeeting();
}
},
If I now type a letter I get this in the logs:
Changed meeting Tue Dec 04 2018 19:14:43 GMT+0100
Now setting meetingSaved to true
Done setting meetingSaved to true
The logs are as expected, but the button itself is never disabled. If I remove the watcher the button is always disabled however. Even though the watcher first sets this.meetingSaved to false, and then this.saveMeeting() sets it to true, adding the watcher somehow never disables the button.
What am I doing wrong here?
Edit
Here's a paste of the whole page: https://pastebin.com/x4VZvbr5
You've got a few things going on that could use some changing around.
Firstly the data attribute should be a function that returns an object:
data() {
return {
selectedMeeting: {
content: null
},
meetings: [],
meetingSaved: true,
saveMeetingButton: {
saveText: 'Save Meeting',
savedText: 'Saved',
disabled: true
},
};
}
This is so Vue can properly bind the properties to each instance.
Also, the content property of the selectedMeeting didn't exist on the initial render so Vue has not added the proper "wrappers" on the property to let things know it updated.
As an aside, this can be done with Vue.set
Next, I would suggest you use async/await for your promises as it makes it easier to follow.
async created() {
const response = await axios.get('/ajax/meetings');
this.meetings = response.data;
this.selectedMeeting = this.meetings[0];
this.meetingSaved = true;
},
For your method I would also write as async/await. You can also use Vue modifiers like once on click to only call the api if there is no previous request (think a fast double-click).
methods: {
async saveMeeting () {
const response = await axios.post('/ajax/meetings/' + this.selectedMeeting.id, this.selectedMeeting);
this.selectedMeeting = response.data;
console.log('Now setting meetingSaved to true');
this.meetingSaved = true;
console.log('Done setting meetingSaved to true');
},
},
The rest of the code looks okay.
To summarize the main problem is that you didn't return an object in the data function and it didn't bind the properties reactively.
Going forward you are going to want to debounce the text input firing the api call and also throttle the calls.
this.meetingSaved = true;
this is referencing axios object. Make a reference to vue object outside your call and than use it. Same happens when you use jQuery.ajax().
created() {
var vm = this;
axios.get('/ajax/meetings')
.then(response => {
vm.meetings = response.data;
vm.selectedMeeting = vm.meetings[0];
vm.meetingSaved = true;
});
},
I'm building an ember app with email like messaging.
When a user is sending a message the container should scroll
to the bottom to show the new message. Here's the action that creates the new message
createMessage(messageParams) {
this.get('store').createRecord('message', messageParams).save()
.then(() => {
return this.refresh()
})
.then(() => {
let objDiv = document.getElementById("message-container");
objDiv.scrollTop = objDiv.scrollHeight;
})
.catch(() => {
this.get('flashMessages')
.danger('There was a problem. Please try again.');
});
As of now the chat box does not scroll to the bottom when the new message appears in the container. It works when I wrap the code in the second .then in a set timeout.
Should I put the code that scrolls the container to the bottom somewhere else in route file? I tried putting it in the afterModel hook. I also tried putting it in a .then after the model hook. When I did that, I got a blank white page when navigating to the route in the app.
model(params) {
return Ember.RSVP.hash({
messages: this.get('store').query('message', {
id: params.conversation_id
}),
conversation: this.get('store').find('conversation', params.conversation_id)
}).then(() => {
let objDiv = document.getElementById("message-container");
objDiv.scrollTop = objDiv.scrollHeight;
});
}
In didTransition hook, you can run DOM code in run loop afterRender queue.
didTransition() {
Ember.run.later('afterRender', () => {
let objDiv = document.getElementById("message-container");
objDiv.scrollTop = objDiv.scrollHeight;
}, 100);
return true;
},
DOM manipulation code should go in Component, I prefer you to write component for showing the message and make use of the component lifecycle hook methods(i.e, in your case didRender would be best fit)
I just have a question to find out if this is possible:
So what I am doing is when I submit a post I wait for it to complete then update the user object in firebase to insert a time-stamp.
This is fine and works but when the time-stamp is inserted it is causing other subscriptions that are subscribed to changes in the user object to update.
What I want to do it update the user object without causing other subscribers to be updated.
Here is where I am updating the timestamp:
I tried commenting out this line of code which stops the data duplication issue on screen but I need this code to run as I need to update the timestamp when a post is submitted.
this.af.database.object('users/' + x[0].uid + '/lastPostAt').set(timestamp)
.then(x => { this.dialogsService.showSuccessDialog('Post Submitted'); });
Here is where I am subscribing to all the posts:
subscribeAllPosts(): Observable<Post[]> {
return this.af.database.list('/posts')
.map(Post.fromJsonList);
}
Here is where I am creating the array of posts in my constructor to display via a loop in the html:
this.activeItem = this.items[0];
this.postsService.subscribeAllPosts()
.subscribe(posts => {
let container = new Array<PostContainer>();
for (let post of posts) {
this.getEquippedItemsForUsername(post.username).subscribe(
x => {
try {
container.push(new PostContainer(post, x[0].equippedItems));
} catch (ex) { }
}
);
}
this.postContainers = container;
});
In the inner subscription it gets the equippedItems for the user of the post:
getEquippedItemsForUsername(username: string) {
return this.usersService.subscribeUserByUsername(username);
}
Which in turns calls:
subscribeUserByUsername (username: string) {
return this.af.database.list('users' , {
query: {
orderByChild: 'username',
equalTo: username
}
});
}
In the HTML it loops through the postContainer[]:
<li *ngFor="let item of postContainers">
So all that works the issue as stated is that if I do not have the following commented out then the posts will be duplicated more and more as the posts are submitted. If I refresh the app then the posts will show the correct non duplicated posts until another post is submitted.
this.af.database.object('users/' + x[0].uid + '/lastPostAt').set(timestamp)
.then(x => { this.dialogsService.showSuccessDialog('Post Submitted'); });
EDIT: Solved by splitting up logic.