I've been looking at many two-way data binding libraries and so far haven't found one that will fire onchange events when the input's value is set from a change on the model. Is there any way to do that with ractive.js?
It's possible, but is a little bit hacky. Browsers only fire the change event as a result of user interaction (rather than input.value = 'someNewValue'), so you have to watch the model and fire the event yourself:
var ractive = new Ractive({
el: 'main',
template: '#template',
data: { name: 'world' },
twoway: false
});
ractive.observe( 'name', function () {
// name has changed, need to simulate an event
var event = new Event( 'change' );
ractive.find( 'input' ).dispatchEvent( event );
});
ractive.find( 'input' ).addEventListener( 'change', function ( event ) {
console.log( 'event was fired', event );
});
// check the console!
ractive.set( 'name', 'everybody' );
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<main></main>
<script id='template' type='text/ractive'>
<h1>Hello {{name}}!</h1>
<input value='{{name}}'/>
</script>
Note that twoway binding has been disabled, otherwise you'd get extra events firing all over the place when the user did interact with the input - so you would need to listen for input/change events and handle those interactions yourself.
The answer (for my purposes) is actually quite simple. First a little background - which I probably should have outlined in the original question. Let's say you're viewing/editing a customer profile. Meanwhile someone else is doing the same. They update a phone# and resave the profile. Without anything special being done you aren't going to see that new phone# unless you reload the profile. My goal is to make our data/forms 'reactive'. One of the difficulties was updating a form. In itself that's easy enough but how to handle any onchange events on inputs. Let's say a country is changed so a new list of regions needs to appear. Me changing the country would fire off the country's onchange event and a new list would appear. If a reactive change occurred and updated the country the onchange wouldn't fire. That's a problem. To make a long story short, the answer is to not have any onchange events on inputs but have a Ractive.on('change') event and then parse the key path for anything of interest. That will catch changes from the human and changes from the 'beyond', either with set or merge.
var cust = {
firstname: 'Fred',
lastname: 'Flintstone'
}
ractive = new Ractive({
el: '#spot',
template: '#tmpl1',
data: {customer: cust},
lazy: true
})
ractive.on('change', function(kp) {
console.log(kp)
})
setTimeout(function() {
ractive.set('customer.firstname', 'Wilma')
}, 2000)
setTimeout(function() {
ractive.merge('customer', {firstname: 'Barney', lastname: 'Rubble'})
}, 4000)
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<body>
<div id='spot'></div>
<script id='tmpl1' type='text/tmpl'>
<input value={{customer.firstname}}>
<input value={{customer.lastname}}>
</script>
</body>
Related
Banging my head on how to use polymer two way binding.
I have a home made Polymer element that defines a boolean property through
Polymer({
is: "test-element",
ready: function() {},
properties: {
propEnabled: {
type: Boolean,
notify: true,
value: false,
observer: "propEnabledChanged"
}
},
// Called when aoEnabled is changed
propEnabledChanged: function() { console.log("propEnabled value switched to " + this.propEnabled); },
});
Now I'm using this in an HTML page
<body>
<template id="t" is="dom-bind">
<test-element id="testElement"></test-element>
<paper-toggle-button checked="{{Model.propEnabled}}">prop. enabled</paper-toggle-button>
<button id="switchInternalState">switch state</button>
</template>
</body>
<script>
var t = document.querySelector('#t');
document.addEventListener('WebComponentsReady', function() {
console.log('WebComponentsReady');
// We have to bind the template with the model
var t = document.querySelector('#t');
t.Model = document.getElementById("testElement");
// chaging the property directly does not reflect in the GUI... :-(
var button = document.getElementById("switchInternalState");
button.addEventListener("click", function() {
t.Model.set("propEnabled", !t.Model.propEnabled);
});
});
</script>
But when clicking on the switch state button...
I get the log propEnabled value switched to true
But the toogle button on the page does not change...
If I add a simple
<label>{{Model.propEnabled}}</label>
The label does not change either...
To me it looks a bit like one way binding where it should be 2 way as
toggling the button fire the log and properly change the component propEnabled value. So it really looks like one way binding to me.
So... How can we actually benefit from two way binding with Polymer templates ????
You need to assign the propEnabled property from dom-bind to the test-element through html.
<test-element id="testElement" prop-enabled={{propEnabled}}></test-element>
<paper-toggle-button checked="{{propEnabled}}">prop. enabled</paper-toggle-button>
Also you don't need the variable t.Model in your script. You can remove it. The event listener should be like below
button.addEventListener("click", function() {
t.propEnabled = !t.propEnabled;
});
The following plunker has the working code: http://embed.plnkr.co/13QJ7QFETIBg4bEiCMS7/preview
I'm trying to use Meteor to build a calendar app, but having issues about initial loading of data from database.
At the very beginning, I just used the autopublish, which caused the problem because the subscription might not be ready yet. Then I looked at the questions here Can't put data from a Meteor collection into an array and here Meteor: How can I tell when the database is ready? and make some changes of my code to this:
Meteor.startup(function(){
Session.set("data_loaded", false);
});
Meteor.subscribe("allEvents", function() {
Session.set("data_loaded", true);
});
var myCalendar = null;
Template.Calendar.onRendered(function() {
if (Session.get("data_loaded")) {
myCalendar = $('#myCalendar').fullCalendar({
header: {
left: 'prev,next,today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultView: 'agendaWeek',
dayClick: function(date, jsEvent, view) {
CalEvents.insert({title:"New Event", start:date.format(), end:date.format()});
Session.set("lastMod", new Date());
},
eventClick: function(calEvent, jsEvent, view) {
},
events: function(start, end, timezone, callback) {
var events = [];
allEvents = CalEvents.find({},{reactive:false});
console.log(allEvents);
allEvents.forEach(function(evt){
console.log(evt);
events.push({
id:evt._id,
title:evt.title,
start:evt.start,
end:evt.end});
});
callback(events);
}
}).data().fullCalendar;
}
myCalendar.defaultView = 'agendaWeek';
});
Template.Calendar.lastMod = function() {
return Session.get("lastMod");
};
However, I'm still having the same problem, just at this time, instead of showing a blank calendar, it doesn't show calendar at all in most cases. I feel like I'm not setting Session correctly, especially for that if statement, but I'm not very sure how to do that.
And then, I found this post Displaying loader while meteor collection loads , and followed the step there to make a template-level subscriptions. However, I got another error.
var myCalendar = null;
Template.Calendar.onCreated(function(){
this.subscribe("allEvents");
});
Template.Calendar.onRendered(function() {
myCalendar = $('#myCalendar').fullCalendar({
header: {
left: 'prev,next,today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultView: 'agendaWeek',
dayClick: function(date, jsEvent, view) {
CalEvents.insert({title:"New Event", start:date.format(), end:date.format()});
Session.set("lastMod", new Date());
},
eventClick: function(calEvent, jsEvent, view) {
},
events: function(start, end, timezone, callback) {
var events = [];
allEvents = CalEvents.find({},{reactive:false});
console.log(allEvents);
allEvents.forEach(function(evt){
console.log(evt);
events.push({
id:evt._id,
title:evt.title,
start:evt.start,
end:evt.end});
});
callback(events);
}
}).data().fullCalendar;
myCalendar.defaultView = 'agendaWeek';
});
Template.Calendar.lastMod = function() {
return Session.get("lastMod");
};
template file:
<template name="Calendar">
{{#if Template.subscriptionReady}}
{{> editEvent}}
<br>
<br>
<input type="hidden" name="lastMod" value="{{lastMod}}" id="lastMod">
<div id="myCalendar">
</div>
{{else}}
Loading...
{{/if}}
</template>
When the application starts, it shows the "Loading..." characters, but then I got a TypeError: Cannot read property 'fullCalendar' of undefined from }).data().fullCalendar;
Anyone can help me to get this thing work? Thanks in advance.
Short answer: "meteor search" is your friend. Use the component that I linked in the comment.
Long answer: Getting UI elements to work that modify DOM or create entire DOM trees is tricky with meteor. The overall problem is that meteor doesn't have a single callback which gets called when the DOM is finalized. In fact, DOM is never finalized and could change at any point. Any custom nodes that a UI component created and were in the subtree will get destroyed and will have to be recreated. While it can be done, it is quite a bit of plumbing and isn't easily understood by the newly initiated to meteor.
Luckily however, most of the frequent used UI components already have a meteor smart package and using them is just a matter of doing a "meteor add". Even a bit more experienced ones out of us prefer to simply save time and if a package already have the plumbing of the component sorted out, would just use that instead of spending a few hours trying to get it to work from scratch.
I am using pnotify and loading callback function to show a notification when the fullcalendar plugin has loaded all events.
loading:function(isLoading, view){
if (isLoading === false){
new PNotify({
title:"Finished loading events",
type:'success',
delay: 1000
});
My problems is that when ever I move to different dates it calls loading again so I am left with so many notifications shown on my screen that it becomes very unusable. How can I bypass this? Is there a way to check if a notification is active and just change the text and title of it?
You can add that logic based on the template you're using (check the template docs).
Your code would be something like
loading:function(isLoading, view){
var exists = false;
$(".ui-pnotify-title").each(function() {
if ($(this).html() == 'Finished loading events')
exists = true;
});
if (!exists) {
new PNotify({
title:"Finished loading events",
type:'success',
delay: 1000
});
}
}
It would be better if you could use a specific id or class to detect if the notification is already shown, but this works.
Take a look at the working jsfiddle.
You can just store it in a variable, do your necessary code (like nullable/undefined checks, etc) and call "update()" (here: http://sciactive.com/pnotify/ - for example, find for 'Click Notice' and see the source)
var p = new PNotify({
title: 'Some title',
text: 'Check me out! I\'m a error.',
type: 'error',
icon: 'fa fa-times-circle'
});
// ... code ...
p.update({title: 'My new title'});
I am using Reactive-Table to display data saved in my Meteor app as shown from the code below, in each row of the table there is a link to edit the document related to this row. I am trying using the edit link 'click event' to capture the _id of the document related to the row being selected but can't seem to get the _id, can someone please check my code and let me know what I am missing / doing wrong here and how to capture the _id? Thanks
customerslist.html
<template name="customerslist">
<div class="customerslist">
<div class="page-header">
<h1>Customers List</h1>
</div>
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=customersCollection settings=settings}}
</div>
</div>
</template>
customerslist.js
Template.customerslist.helpers({
customersCollection: function () {
return Customers.find({},{sort: {title: 1}});
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: 'name', label: 'Customer Name' },
{ key: 'email', label: 'Email' },
{ key: 'phone', label: 'Phone' },
{ key: '_id', label: 'Action', sortByValue: false, fn: function(_id){ return new Spacebars.SafeString('<a name="' + _id +'" class="edtlnk" target="_blank" href="' + _id + '/edit"> Edit </a>'); } }
]
};
}
});
Template.customerslist.customers = function () {
return Customers.find({},{sort: {title: 1}});
};
Template.customerslist.events({
'click .edtlnk': function(e) {
var cust = this;
event.preventDefault();
console.log('Customer ID: '+cust._id);
}
});
The way the package sets up data contexts, this will only be set to the customer object if the event selector matches the tr element. That makes event.currentTarget the tr, but event.target is still the edit link.
You could try something like this:
Template.customerslist.events({
'click .customerslist tr': function(e) {
if ($(e.target).hasClass('edtlnk')) {
var cust = this;
e.preventDefault();
console.log('Customer ID: '+cust._id);
}
}
});
I don't know Meteor though I am starting to play around with it so I don't care about up or down votes at all but learning the answer your question.
I found the Event Maps docs which I am sure you saw as well:
https://docs.meteor.com/#/full/eventmaps
This was listed in the doc:
{
'click p': function (event) {
var paragraph = event.currentTarget; // always a P
var clickedElement = event.target; // could be the P or a child element
}
}
If a selector matches multiple elements that an event bubbles to, it will be called multiple times, for example in the case of 'click
div' or 'click *'. If no selector is given, the handler will only be called once, on the original target element.
The following properties and methods are available on the event object passed to handlers:
type String
The event's type, such as "click", "blur" or "keypress".
target DOM Element
The element that originated the event.
currentTarget DOM Element
The element currently handling the event. This is the element that matched the selector in the event map. For events that bubble, it may be target or an ancestor of target, and its value changes as the event bubbles.
It seems to me that since target and currentTarget are DOM elements can't you get what you need from those or are you referring to the _id available in Meteor on an insert callback?
I might be wrong about what is actually happening here but i have 3 Html.dropdownlists. And im using jquery to handle filtering which does actually work. However, there is some odd behaviour which i think might be because data isnt finished loading before the next function is called.
For instance:
Some background.
Company: Owns several field offices
Field Office: Owns several facilties
So logically, when you change company, field offices should change, which then changes facilities.
$(function () {
$(document).ready(function () {
var cid = $("#CompanyId").val();
$.post("/ManifestSearch/GetFilteredFieldOffices", { id: cid }, function (data) {
$("#FieldOfficeId").loadSelect(data);
});
var fid = $("#FieldOfficeId").val();
$.post("/ManifestSearch/GetFilteredFacilities", { id: fid }, function (data) {
$("#FacilityId").loadSelect(data);
});
});
});
Now, when the page loads, everything looks fine. All the dropdownlists have the correct data.
When i change company, this calls.
$(function () {
$('#CompanyId').change(function () {
var cid = $(this).val();
$.post("/ManifestSearch/GetFilteredFieldOffices", { id: cid }, function (data) {
$("#FieldOfficeId").loadSelect(data);
});
var fid = $("#FieldOfficeId").val();
$.post("/ManifestSearch/GetFilteredFacilities", { id: fid }, function (data) {
$("#FacilityId").loadSelect(data);
});
});
});
This changes the field offices to the correct list, however facilities changes to whatever field offices was set to before the company change occured. I dont know enough about jquery to figure out exactly what is going on, but my instinct tells me that the two posts are happening at the same time, and the second post happens before the first one is finished.
It's the nature of an asynchronous request.. you don't know which order they will finish in so you can't always assume the data from the first one will be available to the second one.
Ideally, your second request should be within the onSuccess callback function of the first request, with an onFailure/onError function handler to take care of any problems that arise.