I am using the Meteor reactive framework to allow a user to edit a text box in a web gui, update the database on any changes on the text box, and update the textbox with updates from the database.
This creates a dependency loop, and when I type fast, the latency between the updates destroys text written by the user.
My thought of how to alleviate this is to temporarily pause updates from the database to any object that the user has focused on.
I have tried numerous ways to do this. Here is my template:
<template name="valueEditor">
<div class="list-item {{editingClass}}">
<input type="text" value="{{value}}" placeholder="value">
</div>
</template>
Here are the helpers:
Template.valueEditor.helpers({
value : function(){
var state = ! Session.equals(EDITING_KEY, this._id);
console.log("reactive state = " + state)
var result = Objects.find({_id:this._id},{reactive:state}).fetch()[0].value;
console.log("Database is emitting '" + result + "'back to the UI input!!!")
return result;
});
Here are the events:
Template.valueEditor.events({
'keydown input[type=text]': function(event) {
console.log("You've pressed = " + String.fromCharCode(event.which));
if (event.which === 27 || event.which === 13) {
event.preventDefault();
event.target.blur();
}
},
'keyup input[type=text]': _.throttle(function(event) {
console.log("saving '" + event.target.value + "' to database.");
Objects.update(this._id, {$set: {value: event.target.value}});
}, 300)
)};
Here is the output (when I type fast):
"You've pressed = T"
"You've pressed = E"
"You've pressed = S"
"saving 'tes' to database."
"You've pressed = T"
"You've pressed = I"
"reactive state = false"
"Database is emitting 'tes'back to the UI input!!!"
"You've pressed = N"
"saving 'tesn' to database."
"You've pressed = G"
"You've pressed = "
"reactive state = false"
"Database is emitting 'tesn'back to the UI input!!!"
"saving 'tesn' to database."
How can I make it so the database doesn't overwrite the text that I intended to type in the input box???
I found out I could get the element to stop updating from the database by sending back to the html element what's already in the html element when a user is editing it. Here's the code:
Template.objectTemplate.events({
'focus input[type=text]': function(event) {
Session.set(EDITING_VALUE, event.target.value);
Session.set(EDITING_KEY, this._id);
},
'blur input[type=text]': function(event){
if (Session.equals(EDITING_KEY, this._id))
{
Session.set(EDITING_KEY, null);
Session.set(EDITING_VALUE, "");
}
},
'keydown input[type=text]': function(event) {
if (event.which === 27 || event.which === 13) {
event.preventDefault();
event.target.blur();
}
},
'keyup input[type=text]': _.throttle(function(event) {
Objects.update(this._id, {$set: {value: event.target.value}});
}, 300),
});
Template.objectTemplate.helpers({
value : function(){
var x;
if (Session.equals(EDITING_KEY, this._id))
x = Session.get(EDITING_VALUE);
else
x = this.value;
return x
}
Inside your second keypress event, do a timeout and only update the database every x seconds or more.
A solution seems to be in either Tracker.nonreactive(func) (docs) or some flavor of Collection.find({}, {reactive: false});.
There's a discussion of the same question here at the Meteor.com forums and a quick one here.
When I understand better (maybe by looking into the Tracker Manual?), I'll update this answer...
Related
I am doing a school project that need to use jsoup to scrape some datas from certain website.
I saw a button as following attribute:
<a rel="nofollow"
onclick="getRedirectPage(37443499,2206,801)"
class="showtime_btn btn btn-default seat_3"
id="37443499"
href="javascript:void(0)" role="button">06:40PM
<br>
<small>IMAX 3D</small
></a>
//below is the getRedirect method. I was trying to understand it,
but I dont know what language they using, have no idea where to learn the necessary knowledge to understand it, please help me out.
thank you !!
var timeout = "";
function clickRedirect(){
ga('send', 'event', 'Redirect_CTA', 'click', viewModel.popupRedirectAdsEventName());
}
function getRedirectPage(shid, movieid, cinemaid){
$('#page-overlay').show();
ga('send', 'event', 'Movie_SelectShowtime', 'click_showtime', movieid, cinemaid);
$.get("https://www.popcorn.app/sg/showtime/redirect",
{
showtimeid: shid
},
function (data){
setTimeout(function(){
$('#page-overlay').fadeOut();
},100);
var dt = JSON.parse(data);
if(dt.status > 0){
dt = dt.data;
viewModel.popupRedirectToLang(dt.lang_redirectto.replace('%s', dt.site));
viewModel.popupDisclaimer(dt.lang_disclaimer);
if(dt.redirect_ads != undefined && dt.redirect_ads.event_name != undefined){
viewModel.isExternalTraffic(true);
viewModel.popupRedirectAdsUrl(dt.redirect_ads.url);
viewModel.popupRedirectAdsLabel(dt.redirect_ads.label);
viewModel.popupRedirectAdsEventName(dt.redirect_ads.event_name);
}else{
viewModel.isExternalTraffic(false);
}
viewModel.popupRedirectNote(dt.lang_redirectnote);
viewModel.popupFollowUrl(dt.follow);
viewModel.popupLangBtn(dt.lang_btn_txt);
viewModel.targetBlank("_blank");
//campaign:
if(dt.campaigns != undefined && dt.campaigns.trackingUrl != undefined){
viewModel.isCampaignValid(true);
viewModel.adImage(dt.campaigns.adImage);
viewModel.campaignName(dt.campaigns.campaignName);
viewModel.isSmallerFont(dt.campaigns.isSmallerFont);
viewModel.campaignPercentage(dt.campaigns.campaignPercentage);
viewModel.companyName(dt.campaigns.companyName);
viewModel.campaignAddress(dt.campaigns.campaignAddress);
viewModel.campaignTrackingUrl(dt.campaigns.trackingUrl);
viewModel.discountedPrice(dt.campaigns.discountedPrice);
viewModel.originalPrice(dt.campaigns.originalPrice);
}else{
viewModel.isCampaignValid(false);
}
$("#redirectPopupTarget").show();
$(".close_btn").click(function(){
clearTimeout(timeout);
$("#redirectPopupTarget").hide();
});
timeout = setTimeout(function(){
window.location.href = dt.follow;
},5000);
}
})
.error(function (er) {
console.log("Error:", er.responseText);
});
}
$(document).ready(function(){
$("#redirectPopupTarget").css('height', $(window).height());
});
enter image description here
below link is the id = redirectPopupTarget
This is javascript with jQuery. This code does some other things, but I think you are most interested in getting redirect link.
This is done by sending request to https://www.popcorn.app/sg/showtime/redirect?showtimeid=shid. Where shid is first parameter of you javascript function (https://www.popcorn.app/sg/showtime/redirect?showtimeid=37443499).
The response to this request is in form of JSON and element you are looking for is in the field follow
I have two separate form input's (both text type), one in template A, and one in template B. Template A invokes template B. All the specific names/properties of these two input form's are unique. I have event handlers for both, within their own properly named Template.name.events().
When I build a very simple test case of this, no problems, everything works fine. But in my larger and more complex actual app, when I enter text into the template B form, the correct template B submit event handler gets invoked. And then...the template A submit event handler gets invoked! This happens even when I do nothing but an event.PreventDefault call in handler B (side question: are event handlers ever invoked for reactive reasons, or strictly "event occurred" reasons?). I am able to work around this odd behavior for the moment by checking in the A event handler for an undefined name property and just exiting if that's the case, but that's just a kludge for something wrong somewhere. Any suggestions as to a likely culrprit for this odd behavior in my code? Thanks!
Here's the code for the two templates in the failing case; the first (entryHall, with the "new-room" form input) is the "A" template, the second (knock, with the "knock-room" form input) the "B" template. Underneath the event handling code for those two templates is the html+handlebars code for the template definitions and invocations. Sorry for the verbosity and lack of a simpler failing case!
Template.entryHall.events({
// NEW ROOM REQUEST PROCESSING
"submit.new-room": function (event) {
event.preventDefault();
if (event.target.roomName === undefined) {
console.log ("in submit new room and roomname in event is undefined");
return;
}
var rName = event.target.roomName.value;
// Is there already a room name of this same name in the Rooms collection?
var roomsCursor = Rooms.findOne({ roomName: rName });
if (roomsCursor != null) {
// It's a dup; don't allow it
event.target.roomName.value = "Duplicate room name, try again";
return(null);
}
var uName = Session.get ('userName');
// It's a unique name, put it in the Rooms collection.
Rooms.insert({
roomName : rName,
owner : uName,
members: [], // an array of user names
knockRequests: [], // an array of user names
chat : null,
files : null
});
// We have the room document added to the Rooms collection, now we have to
// add the room to the owned list for the user
var userEntry = PZUsers.find({ userName : uName }).fetch();
PZUsers.update({ _id : userEntry[0]._id},
{ $push: { ownedRooms: rName }});
ownedRoomCount++;
roomReactor.changed();
event.target.roomName.value = "";
}
});
Template.knock.events({
// Knock on a room request processing
"submit.knock-room": function (event) {
// Prevent default browser form submit
event.preventDefault();
var knockName = event.target.knockName.value;
event.target.knockName.value = "";
console.log("in knock room submit!");
// Can only knock on a room that exists!
var knockRoomCursor = Rooms.findOne({ roomName: knockName });
if (knockRoomCursor == null) {
console.log ("no such room found to knock on");
return;
}
// Add a knock request to this room, and add this room the the user's list of "open knocks" rooms
var roomEntry = Rooms.find({ roomName : knockName }).fetch();
console.log ("_id of room: " + knockName + " is: " + roomEntry[0]._id);
Rooms.update({ _id : roomEntry[0]._id },
{ $push: { knockRequests: Session.get('userName') }});
roomReactor.changed();
}
});
And here's the invoking html:
<template name="entryHall">
<h2>Welcome {{userName}}</h2>
<h3>Create a new room:</h3>
<div class="roomName">
<form class="new-room">
<input type="text" name="roomName" id="roomName" placeholder="Select a room name" />
</form>
</div>
{{markNoOwnedRooms}}
{{#each ownedRooms}}
{{#if firstOwnedRoom}}
<h3>Enter one of your own rooms:</h3>
{{/if}}
{{ > room }}
{{/each}}
{{markNoMemberRooms}}
{{#each memberRooms}}
{{#if firstMemberRoom}}
<h3>Enter one of your member rooms:</h3>
{{/if}}
{{ > room }}
{{/each}}
<h3>Knock to request entry:</h3>
{{ > knock }}
</template>
<template name="room">
<li>{{this}}</li>
</template>
<template name="knock">
<div class="knockName">
<form class="knock-room">
<input type="text" name="knockName" id="knockName" placeholder="Enter room name" />
</form>
</div>
</template>
I'm trying to re-render a collection of words in a column every time a word is submitted or deleted. It re-renders when the word is deleted but not when a word is submitted.
Here's my template:
<template name = "wordColumn">
{{#each words}}
<button class = "label label-default" draggable="true">
{{word}}
</button>
{{/each}}
</template>
Here's my helper:
Template.wordColumn.helpers ( {
words: function () {
var words;
var wordIds = Session.get ( "wordIds" );
words = Words.find( { '_id': { $in: wordIds } }, { sort: { createdAt: -1 } } );
return words;
},
} )
And here is the word submit code within the template events. When I do Words.find(), it's clear that the underlying database has changed so Words.find() above should be reactive and update the column automatically:
"submit .new-word": function ( e ) {
var text = e.target.word.value;
Meteor.call ( "addWord" , text, function ( err, data ) {
Session.set ( "displayInMainBox", Words.find( data ).fetch()[0] );
} );
e.target.word.value = "";
return false;
}
Yet no cigar - the word I just submitted doesn't display automatically unless I refresh the page.
You can check out the deployed app here: contextual.meteor.com, and submit a new word. It'll show in the main box but not the list of all words on the right-hand side.
You need to have a Session.set( "wordIds"... somewhere in that callback. If that doesn't change, your cursor in your helper won't change. Since your current cursor includes all the current docs, when you delete something it will react.
PS, try doing away with the session var all together. Handle that logic in the subscription if possible (or sub to all, from the look of the app).
I have a Dexterity type that has multiple fieldsets, and the built-in Javascript that allows showing one fieldset at a time when adding or editing is wonderful.
But I'd like to invite the user to walk through the fieldsets in sequence, so my ideal situation would not present the "Submit" button until the last fieldset was visible, instead presenting NEXT> or <PREV and NEXT> buttons until that last fieldset.
I gather that this is a behavior? But I'm at a bit of a loss as to how to add it and how to control it. I'm currently using the default EditForm, and I'd much prefer to just make a tiny tweak, but if it means dropping down to building the form myself, that's OK. I just need to know whether that's the only way to get this addition, which seems unlikely.
The fieldsets can be rigged up to add 'previous' and 'next' buttons with some extra JavaScript magic. Here's what I use in a current project:
var prevnext = {
formTabs: null,
next: function() { prevnext.formTabs.data('tabs').next(); prevnext._scrollToTop(); },
prev: function() { prevnext.formTabs.data('tabs').prev(); prevnext._scrollToTop(); },
_scrollToTop: function() {
$(window).scrollTop(prevnext.formTabs.closest('form').offset().top);
},
showButtons: function(event, index) {
var tabs = prevnext.formTabs.data('tabs'),
index = typeof(index) === 'undefined' ? tabs.getIndex() : index,
current = tabs.getTabs()[index],
count = tabs.getTabs().length;
$('#prevnext_previous').toggle(index !== 0);
$('#prevnext_next').toggle(index !== (count - 1));
$('.formControls:last :submit[name=form_submit]').toggle(index === )count - 1));
},
init: function() {
var tabs;
prevnext.formTabs = $('.formTabs');
tabs = prevnext.formTabs.data('tabs');
if (tabs.getTabs().length > 0) {
if ($('fieldset#fieldset-distribution').length === 0)
return;
$('.formControls:last :submit:first')
.before($('<input id="prevnext_previous" class="context" ' +
' type="button" value="" />')
.val('< Previous')
.click(prevnext.prev))
.before(document.createTextNode(' '));
$('.formControls:last :submit:first')
.before($('<input id="prevnext_next" class="context" ' +
' type="button" value="" />')
.val('Next >')
.click(prevnext.next))
.before(document.createTextNode(' '));
prevnext.showButtons();
tabs.onClick(prevnext.showButtons);
}
}
};
$(prevnext.init());
I'm having a bit of a problem with this code.
The program gives me a list of checkboxes but a user ID. then u user can change his selection and push the save button (id="btnSaveUserIntersts") and i am trying to save in the hidden textbox all the values of the checkboxes that was choosen.
The problem is that i am getting all the time the same selections that came form the database and not getting the new selection that the user made.
Can any one tell me what am i doing wrong here?
$(document).ready(
function()
{
$('#btnSaveUserIntersts').bind(
'click',
function()
{
var strCheckBoxChecked = new String();
$('input[type=checkbox][checked]').each(
function()
{
strCheckBoxChecked += $(this).val();
strCheckBoxChecked += ',';
}
);
$('#hidUserInterests').val(strCheckBoxChecked);
}
);
}
);
10x
Try using:
$('input:checkbox:checked').each(
function()
{
strCheckBoxChecked += $(this).val();
strCheckBoxChecked += ',';
}
);
As the selector instead of what you're currently using.
$('input[type="checkbox"]:checked')
Use .map, it's far prettier:
var strCheckBoxChecked = $('input:checkbox:checked').map(function()
return this.value;
}).get().join(",");
And the selector you are using is close, $('input[type=checkbox][checked=checked]')