I'm going through the leaderboard example right now, and I've finished it, but I'm not fully satisfied with my implementation of the add functionality.
To start with, we have
Template.player.events({
'click': function () {
Session.set("selected_player", this._id);
}
});
I find it a little bit confusing how this is associated with the player collection, but I imagine this has to do with the <template part. I am also able to do
Template.leaderboard.events({
'click input.delete': function () {
Players.remove(this._id);
}
...which does remove the player with the associated button entry.
Now for the actual question part: I have added this to the bottom of the leaderboard template:
<div>
Add player: (Name <input required name="name" id="name">)
(Score <input required name="score" id="score">)
<input class="add" type="button" value="Add">
</div>
This works fine, and I have Template.leaderboard.events['click input.delete'] working fine, but in order to get the values I use:
'click input.add': function () {
var name = document.getElementById('name').value,
score = document.getElementById('score').value;
It would make a lot of sense to me if I were able to use this in some way, or use the event to somehow get the values that correspond the inputs. This not only makes sense to me from a design standpoint, but it would also cover the case of having more than one of these kinds of forms displaying simultaneously.
So in short is there any way to get elements that are near the target element in the context of an event?
Every event handler is given two arguments: event and template. You can read more about these event handler arguments here: http://docs.meteor.com/#eventmaps
event.target is a reference to the DOM element that originated the event. You can then use something like jQuery's traversing functions to get an element nearby.
You could also set the input values as properties of the template instance. E.g. in the template's created handler, you create name and score properties:
Template.player.created = function() {
this.name = '';
this.score = '';
};
And then you update those values in the keyup events of your input textboxes:
'keyup #name': function(event, template) {
template.name = event.target.value;
},
'keyup #score': function(event, template) {
template.score = event.target.value;
}
This is the way the same way that widgets made for Ember update their values, as explained here: http://www.emberist.com/2012/04/12/two-way-binding-to-the-dom.html
Nice to see someone with so much street cred using Meteor! The best way to get the value is with event.currentTarget and to get stuff from the data contexts there is also another way which needs no DOM knowledge
Template.player.events({
'keypress #name':function(event,context) {
//Get the event sending this' value
console.log(event.currentTarget.value)
//Traverse the DOM on the template 'player' in this case
console.log(context.find('#score').value)
}
});
Basically the best way to get the value of the sender is to use event.currentTarget to access the DOM for that object sending the event.
The reason it's implemented this way is probably because any dom object can send an event and it won't necessarily always have a value field so a slight bit of knowledge of the DOM is required when handling the event maps but using event.currentTarget.value works for most form fields
Data contexts
Regarding the data contexts you should be able to use the data available in the templates from the helpers, e.g if theres a {{score}} & a {{name}} value in the template or a helper, which is passed in this case via the {{#each}} for each individual player.
this.name,
this.score;
Which is also the same as (I usually use context in my helper but template is another way of callng it i guess like in travellingprog's answer)
context.data.name,
context.data.score;
The this helps get data from the template's data context into event's so that one doesn't have to use hidden HTML attributes containing data, e.g with how the player is removed its a bit cleaner than storing the _id in the dom somewhere. That being said event.currentTarget or context.find(..) are the best way to get the data from a textfield.
Related
I'm trying to use native web components for one of my UI project and for this project, I'm not using any frameworks or libraries like Polymer etc. I would like to know is there any best way or other way to communicate between two web components like we do in angularjs/angular (like the message bus concept).
Currently in UI web-components, I'm using dispatchevent for publishing data and for receiving data, I'm using addeventlistener.
For example, there are 2 web-components, ChatForm and ChatHistory.
// chatform webcomponent on submit text, publish chattext data
this.dispatchEvent(new CustomEvent('chatText', {detail: chattext}));
// chathistory webcomponent, receive chattext data and append it to chat list
this.chatFormEle.addEventListener('chatText', (v) => {console.log(v.detail);});
Please let me know what other ways work for this purpose. Any good library like postaljs etc. that can easily integrate with native UI web components.
If you look at Web Components as being like built in components like <div> and <audio> then you can answer your own question. The components do not talk to each other.
Once you start allowing components to talk directly to each other then you don't really have components you have a system that is tied together and you can not use Component A without Component B. This is tied too tightly together.
Instead, inside the parent code that owns the two components, you add code that allows you to receive events from component A and call functions or set parameters in Component B, and the other way around.
Having said that there are two exceptions to this rule with built in components:
The <label> tag: It uses the for attribute to take in an ID of another component and, if set and valid, then it passes focus on to the other component when you click on the <label>
The <form> tag: This looks for form elements that are children to gather the data needed to post the form.
But both of these are still not TIED to anything. The <label> is told the recipient of the focus event and only passes it along if the ID is set and valid or to the first form element as a child. And the <form> element does not care what child elements exist or how many it just goes through all of its descendants finding elements that are form elements and grabs their value property.
But as a general rule you should avoid having one sibling component talk directly to another sibling. The methods of cross communications in the two examples above are probably the only exceptions.
Instead your parent code should listen for events and call functions or set properties.
Yes, you can wrap that functionality in an new, parent, component, but please save yourself a ton of grief and avoid spaghetti code.
As a general rule I never allow siblings elements to talk to each other and the only way they can talk to their parents is through events. Parents can talk directly to their children through attributes, properties and functions. But it should be avoided in all other conditions.
Working example
In your parent code (html/css) you should subscribe to events emited by <chat-form> and send event data to <chat-history> by execute its methods (add in below example)
// WEB COMPONENT 1: chat-form
customElements.define('chat-form', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `Form<br><input id="msg" value="abc"/>
<button id="btn">send</button>`;
btn.onclick = () => {
// alternative to below code
// use this.onsend() or non recommended eval(this.getAttribute('onsend'))
this.dispatchEvent(new CustomEvent('send',{detail: {message: msg.value} }))
msg.value = '';
}
}
})
// WEB COMPONENT 2: chat-history
customElements.define('chat-history', class extends HTMLElement {
add(msg) {
let s = ""
this.messages = [...(this.messages || []), msg];
for (let m of this.messages) s += `<li>${m}</li>`
this.innerHTML = `<div><br>History<ul>${s}</ul></div>`
}
})
// -----------------
// PARENT CODE
// (e.g. in index.html which use above two WebComponents)
// Parent must just subscribe chat-form send event, and when
// receive message then it shoud give it to chat-history add method
// -----------------
myChatForm.addEventListener('send', e => {
myChatHistory.add(e.detail.message)
});
body {background: white}
<h3>Hello!</h3>
<chat-form id="myChatForm"></chat-form>
<div>Type something</div>
<chat-history id="myChatHistory"></chat-history>
+1 for both other answers, Events are the best because then Components are loosly
coupled
Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/
Note that in the detail of a Custom Event you can send anything you want.
Event driven function execution:
So I use (psuedo code):
Elements that define a Solitaire/Freecell game:
-> game Element
-> pile Element
-> slot Element
-> card element
-> pile Element
-> slot Element
-> empty
When a card (dragged by the user) needs to be moved to another pile,
it sends an Event (bubbling up the DOM to the game element)
//triggered by .dragend Event
card.say(___FINDSLOT___, {
id,
reply: slot => card.move(slot)
});
Note: reply is a function definition
Because all piles where told to listen for ___FINDSLOT___ Events at the game element ...
pile.on(game, ___FINDSLOT___, evt => {
let foundslot = pile.free(evt.detail.id);
if (foundslot.length) evt.detail.reply(foundslot[0]);
});
Only the one pile matching the evt.detail.id responds:
!!! by executing the function card sent in evt.detail.reply
And getting technical: The function executes in pile scope!
(the above code is pseudo code!)
Why?!
Might seem complex;
The important part is that the pile element is NOT coupled to the .move() method in the card element.
The only coupling is the name of the Event: ___FINDSLOT___ !!!
That means card is always in control, and the same Event(Name) can be used for:
Where can a card go to?
What is the best location?
Which card in the river pile makes a Full-House?
...
In my E-lements code pile isn't coupled to evt.detail.id either,
CustomEvents only send functions
.say() and .on() are my custom methods (on every element) for dispatchEvent and addEventListener
I now have a handfull of E-lements that can be used to create any card game
No need for any libraries, write your own 'Message Bus'
My element.on() method is only a few lines of code wrapped around the addEventListener function, so they can easily be removed:
$Element_addEventListener(
name,
func,
options = {}
) {
let BigBrotherFunc = evt => { // wrap every Listener function
if (evt.detail && evt.detail.reply) {
el.warn(`can catch ALL replies '${evt.type}' here`, evt);
}
func(evt);
}
el.addEventListener(name, BigBrotherFunc, options);
return [name, () => el.removeEventListener(name, BigBrotherFunc)];
},
on(
//!! no parameter defintions, because function uses ...arguments
) {
let args = [...arguments]; // get arguments array
let target = el; // default target is current element
if (args[0] instanceof HTMLElement) target = args.shift(); // if first element is another element, take it out the args array
args[0] = ___eventName(args[0]) || args[0]; // proces eventNR
$Element_ListenersArray.push(target.$Element_addEventListener(...args));
},
.say( ) is a oneliner:
say(
eventNR,
detail, //todo some default something here ??
options = {
detail,
bubbles: 1, // event bubbles UP the DOM
composed: 1, // !!! required so Event bubbles through the shadowDOM boundaries
}
) {
el.dispatchEvent(new CustomEvent(___eventName(eventNR) || eventNR, options));
},
Custom Events is the best solution if you want to deal with loosely coupled custom elements.
On the contrary if one custom element know the other by its reference, it can invoke its custom property or method:
//in chatForm element
chatHistory.attachedForm = this
chatHistory.addMessage( message )
chatHistory.api.addMessage( message )
In the last example above communication is done through a dedecated object exposed via the api property.
You could also use a mix of Events (in one way) and Methods (in the other way) depending on how custom elements are linked.
Lastly in some situations where messages are basic, you could communicate (string) data via HTML attributes:
chatHistory.setAttributes( 'chat', 'active' )
chatHistory.dataset.username = `$(this.name)`
I faced the same issue and as I couldn't find any fitting library I decided to write one on my own.
So here you go: https://www.npmjs.com/package/seawasp
SeaWasp is a WebRTC data layer which allows communication between components (or frameworks etc).
You simply import it, register a connection (aka tentacle ;) ) and you can send and receive messages.
I'm actively working on it so if you have any feedback /needed features, just tell me :).
For the case where the parent and child know about each other, like in a toaster example.
<toaster-host>
<toast-msg show-for='5s'>Success</toast-msg>
</toaster-host>
Lots of options but for:
Parent passing data to the child -> attributes or observedAttributes for primitives. If complex objects need to be passed either expose a function or a property aka domProperty that can be set. If a domProperty needs to react to being updated, it can be wrapped in a proxy.
Child passing data to parent -> can use events, or can query for the parent using .closest('toaster-host') and call a function or set a property. I prefer to query and call a function. Typescript helps with this type of approach.
In cases like the toaster example, the toaster-host and the toast-item will always be used together, so the argument about loose coupling is academic at best. They are different elements mainly because they have different jobs. If you wanted to swap out implementations of the toast-msg you could do that when you define the custom element, or even by changing the import statement to point to a different file.
I have a simple application form. On click of one button I just need to add text fields and on click of another button, just remove text field dynamically.
How can this be done in meteor without using jQuery as I have seen many blogs that says it is not a good practice to use jQuery with meteor. Can any tell me how can this be achieved without using jQuery.
You can use a reactive variable and a helper that returns an array based on that reactive variable to construct template-level {{#each}} statements. A good choice for a reactive variable is the Session variable, since it's built into Meteor (you won't need the ReactiveVar package or to set up your own dependencies).
Then, you can use event handlers to update the reactive variable as appropriate. For example...
//client only code
Template.test.onCreated(function() {
Session.set('inputs', []); // on page load, set this to have no inputs
});
Template.test.helpers({
inputs: function () {
return Session.get('inputs'); // reactively watches the Session variable, so when it changes, this result will change and our template will change
}
});
// Now we'll set up a click handler to add inputs to our array when we click the "add" button
Template.test.events({
'click #add-input': function () {
var inputs = Session.get('inputs');
var uniqid = Math.floor(Math.random() * 100000); // Give a unique ID so you can pull _this_ input when you click remove
inputs.push({uniqid: uniqid, value: ""});
Session.set('inputs', inputs);
}
});
// We also need handlers for when the inputs themselves are changed / removed
Template.input.events({
'click .remove-input': function(event) {
var uniqid = $(event.currentTarget).attr('uniqid');
inputs = Session.get('inputs');
inputs = _.filter(inputs, function(x) { return x.uniqid != uniqid; });
Session.set('inputs', inputs);
},
'change input': function(event) {
var $input = $(event.currentTarget);
var uniqid = $input.attr('uniqid');
inputs = Session.get('inputs');
index = inputs.findIndex(function(x) { return x.uniqid == uniqid; });
inputs[index].value = $input.val();
Session.set('inputs', inputs);
}
});
Your templates would look something like...
<template name="test">
<button id='add-input'>
Add Input
</button>
{{#each inputs}}
{{> input}}
{{/each}}
</template>
<template name='input'>
<input name='testinput' class='test-input' type='text' uniqid="{{uniqid}}" value="{{value}}">
<button class='remove-input' uniqid="{{uniqid}}">Remove</button>
</template>
As per Ibrahim's comment below, if you want to delete the text fields, you'll need to keep track of the values in the text fields and repopulate them every time you delete an element. You can see the full work-up in action here. Note that in order to do this, I cheated and actually did use jQuery, because it was way easier to do it that way (at least for me).
A jQuery-less alternative might involve rigging up the onCreated function to store a reference to each input template instance, from which you might be able to pull the necessary information, but per this question there is no way to get all instances of a particular template through the Meteor API, which would be the easiest way to do it without jQuery.
Edit:
MeteorPad no longer exists -- The code above includes handling adding and removing a specific input using the reactive Session variable. I am now maintaining the current value of the input in the Session variable, and I use this new value property to populate the value every time the inputs are re-populated (when the Session variable updates).
You can see that constantly reading stuff off the screen and updating the array of inputs in the Session variable is quite manual and tedious -- which makes me think this is probably not the best way to be doing this.
One possible solution would be to use session variables. When the button is clicked, set the value of the session variable to what you want. In your template you can show the value of the session variable wherever you need.
Also, jquery is automatically included in meteor. There are definitely places to use jquery in meteor apps. May even be cleaner than using session variables in places. Depends on the situation.
What are the implications/potential problems of completing a binding asynchronously?
Consider the following binding:
ko.bindingHandlers.widget = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel)
{
var values = valueAccessor();
//depending on type, delegate binding work to another function
factory.getWidget(values.type)
.done(function (widget) {
widget.bind(element, values.settings, allBindingsAccessor, viewModel);
});
//widget.bind is responsible for all children
return { controlsDescendantBindings: true };
}
};
What are the potential problems that may occur as a result of the init function returning before the binding "work", which includes DOM manipulation, has actually been completed?
I can't clearly see any specific scenario that would cause a problem, but I want to see if anyone else knows something in the way knockout works that may cause problems later.
I was recently working on a similar problem. I have created a custom binding for jsTree that I'm using in my web application. So the UI is -> root -> children -> children's children and so on (on the left)... And for each entity, there is a table that shows some related data (on the right) and a form field showing the fields of the entity. Also, I'm loading both the tree and the data table asynchronously hitting the server with an AJAX call each time the user clicks on a tree node. However, I do have both the init and update methods filled out in my binding handlers.
To help you understand the context a little better, lets take USA as the root node in my tree, USA has many states (root's children) and each state has many counties and so on. Many of the fields inside the root object (USA) will most likely be different than the fields inside its children entities (states, in this case). I'm sorry I can't think of fields that would be there in USA and not in a State, but I'm hoping you get the point.
Also, I have an observable called 'currentSelectedObject' in my VM that gets updated each time the user clicks on a different node in the tree, I make an AJAX call to get the object, and I have a method called replace current object that handles changing the current selected object.. And as mentioned before, parent and child objects have (mostly) different fields. Here's where I ran into a lot of problems. In my html I bind the values of the element like this:
data-bind: value: currentSelectedObject().xyz
Now lets say the user clicks on a different object that doesn't have the field 'xyz,' I used to get this error:
can't read property xyz of undefined.
I got that error because for an extremely small time, the currenySelectedObject has changed and the view (the HTML template) hasn't. I thought the change would be atomic but apparently it isn't. The way I fixed this was by actually updating the current object in the success callback of my AJAX call.
self.replaceCurrentObject = function(type, id, callback){
server.getObject(type, id, // server.js is an abstraction over the data layer
function(returnObject){ // corresponds to: success: function(data){...}
self.currentSelectedObject(returnObject);
}
if(callback) callback(); // to handle other table related stuff
}
I have the following html that is bound to an object containing id and status. I want to translate status values into a specific color (hence the converter function convertStatus). I can see the converter work on the first binding, but if I change status in the binding list I do not see any UI update nor do I see convertStatus being subsequently called. My other issue is trying to bind the id property of the first span does not seem to work as expected (perhaps it is not possible to set this value via binding...)
HTML:
<span data-win-bind="id: id">person</span>
<span data-win-bind="textContent: status converter.convertStatus"></span>
Javascript (I have tried using to modify the status value):
// persons === WinJS.Binding.List
// updateStatus is a function that is called as a result of status changing in the system
function updateStatus(data) {
persons.forEach(function(value, index, array) {
if(value.id === data.id) {
value.status = data.status;
persons.notifyMutated(index);
}
}, this);
}
I have seen notifyMutated(index) work for values that are not using a converter.
Updating with github project
Public repo for sample (not-working) - this is a really basic app that has a listview with a set of default data and a function that is executed when the item is clicked. The function attempts to randomize one of the bound fields of the item and call notifyMutated(...) on the list to trigger a visual updated. Even with defining the WinJS.Binding.List({ binding: true }); I do not see updates unless I force it via notifyReload(), which produces a reload-flicker on the listview element.
To answer your two questions:
1) Why can't I set id through binding?
This is deliberately prevented. The WinJS binding system uses the ID to track the element that it's binding to (to avoid leaking DOM elements through dangling bindings). As such, it has to be able to control the id for bound templates.
2) Why isn't the converter firing more than once?
The Binding.List will tell the listview about changes in the contents of the list (items added, removed, or moved around) but it's the responsibility of the individual items to notify the listview about changes in their contents.
You need to have a data object that's bindable. There are a couple of options:
Call WinJS.Binding.as on the elements as you add them to the collection
Turn on binding mode on the Binding.List
The latter is probably easier. Basically, when you create your Binding.List, do this:
var list = new WinJS.Binding.List({binding: true});
That way the List will call binding.as on everything in the list, and things should start updating.
I've found that if I doing the following, I will see updates to the UI post-binding:
var list = new WinJS.Binding.List({binding: true});
var item = WinJS.Binding.as({
firstName: "Billy",
lastName: "Bob"
});
list.push(item);
Later in the application, you can change some values like so:
item.firstName = "Bobby";
item.lastName = "Joe";
...and you will see the changes in the UI
Here's a link on MSDN for more information:
MSDN - WinJS.Binding.as
Regarding setting the value of id.
I found that I was able to set the value of the name attribute, for a <button>.
I had been trying to set id, but that wouldn't work.
HTH
optimizeBindingReferences property
Determines whether or not binding should automatically set the ID of an element. This property should be set to true in apps that use Windows Library for JavaScript (WinJS) binding.
WinJS.Binding.optimizeBindingReferences = true;
source: http://msdn.microsoft.com/en-us/library/windows/apps/jj215606.aspx
This seems possible as http://www.knockoutjs.com appears to be doing it. I haven't been able to make enough sense of their code-base to get a similar pattern working though.
Effectively I have a MVVM style application with the UI based on jQuery tabs. Each tab is represented by a view model that I want to be able to validate and fire events based on changes in the model.
I create a representation of my data similar to the following on page load:
$(document).ready(function(){
thisTab = new ThisTab();
});
function ThisTab(){
Load: {Load from my model}
Save: {Save/Persist model to the db (via web service call)}
Validate: {
this.Item1 = function(){Validate item 1, do work, refresh fields, whatever.}
}
}
The model itself is a complex global object and changes to the DOM (inputs, etc.) immediately update the object. Changes to some of those properties should call their associated validate items thisTab.Validate.Item1. I have no issue raising events from the changes. If I bind that event listener to a random DOM element I can call my routines without issue and everything works beautifully. It does seem strange, however, to attach the event to a non-related DOM object.
So the question is: how can I do something like thisTab.addEventListner("someEvent") or $(thisTab).bind("someEvent"), where thisTab is not a DOM element, but instead is a native object. Trying to do it, I always get an error that "this method is not supported".
Attaching an event to a standard object does not use the same methods; basically, you would implement your own eventing like so:
function ThisTab()
{
listeners: [],
addListener: function(callback) { this.listeners.push(callback); },
load: { // Finds DOM elements and such, and attaches to their events. The callback from the DOM event should be a method on your object },
yourDomEventCallback: function()
{
for(var j = 0; j < this.listeners.length; j++)
this.listeners[j]();
}
}
The above code should be used as a starting point, since I just cobbled it together and there are likely syntax errors. Basically, you have taken your object and mapped onto events you want to capture, and then expose methods to attach callback methods that you will call when the hidden DOM events occur. You wont be able to use jQuery's abstractions for DOM events, because such events have no meaning on your custom object.
Bind the event to your regular JS object as you would do for a DOM object.
$(thisTab).bind("someEvent", function() {
// handler's code here
});
See this example. Using this has one side-effect that jQuery will add a housekeeping identifier as a property on the object - it looks something like
jQuery1510587397349299863.
This property named jQuery<timestamp> is added to all DOM objects that have events or data associated with them, and regular objects behave similarly. If you are uncomfortable with your model objects being modified, then use your own callback mechanism which should be fairly easy to add.