Best way to store "temp" data on an ember button - javascript

I have an Ember.Button which should remove an element from an array. The button label is simply an X (for now).
I'd like to know the best way to store data for use by the button. If this was plain jquery I might use data-username. But what's the best way to do this?
update
Use case for this question would be something like this:
{{#each App.recentUsersArray}}
<li>
{{#view App.RecentNameBtn contentBinding="this"}} {{content}} {{/view}}
{{#view App.RecentNameDeleteBtn}}X{{/view}}
</li>
{{/each}}
In the second view, I need a way to know which username the delete action should apply to.

Use the {{action}} helper, which passes the context as argument, see http://jsfiddle.net/zVd9g/. Note: in the upcoming Ember.js version the action helper only passes one argument so you would have to adapt your sources accordingly.
If you want to use your existing views, you could do a contentBinding="this" on the App.RecentNameDeleteBtn as you already did on the App.RecentNameBtn.
Handlebars:
<script type="text/x-handlebars" >
{{#each App.arrayController}}
<button {{action "showTweets" target="App.arrayController" }} >{{this}}</button>
<button {{action "removeItem" target="App.arrayController" }}>x</button>
<br/>
{{/each}}
</script>​
JavaScript:
App = Ember.Application.create({});
App.arrayController = Ember.ArrayProxy.create({
content: [1, 2, 3, 4, 5, 6],
removeItem: function(view, evt, item) {
console.log('remove user %#'.fmt(item));
this.removeObject(item);
},
showTweets: function(view, evt, item) {
console.log('show tweets of user %#'.fmt(item));
}
});​

Related

Generating unique variables for #each in an array in order to write an upvote/downvote app

I am brand new to coding so forgive my very obvious ignorance. My question is this: How can I create a unique variable for each item in a global array in MongoDB so that I can tally upvotes and downvotes and sort accordingly. I'm doing all this in the Meteor framework.
Here's my code:
<template name="website_item">
<li>
{{title}}
<p>
{{description}}
</p>
<a href="#" class="btn btn-default js-upvote">
<span class="glyphicon glyphicon-arrow-up" aria-hidden="true"> </span>
</a>
<a href="#" class="btn btn-default js-downvote">
<span class="glyphicon glyphicon-arrow-down" aria-hidden="true"> </span>
</a>
<p>
Votes: {{totalVotes}}
</p>
</li>
</template>
Here's my client.js:
var totalVotes = 0;
Template.website_item.events({
"click .js-upvote":function(event){
// example of how you can access the id for the website in the database
// (this is the data context for the template)
var website_id = this._id;
console.log("Up voting website with id "+website_id);
// put the code in here to add a vote to a website!
totalVotes++;
console.log(totalVotes);
Websites.update({_id:website_id}, {$set: {totalVotes:totalVotes}});
return false;// prevent the button from reloading the page
},
"click .js-downvote":function(event){
// example of how you can access the id for the website in the database
// (this is the data context for the template)
var website_id = this._id;
console.log("Down voting website with id "+website_id);
// put the code in here to remove a vote from a website!
totalVotes--;
console.log(totalVotes);
Websites.update({_id:website_id}, {$set: {totalVotes:totalVotes}});
return false;// prevent the button from reloading the page
}
})
In collection.js I have:
Websites = new Mongo.Collection("websites");
and in server.js I have:
import { Meteor } from 'meteor/meteor';
Meteor.startup(() => {
// code to run on server at startup
if (!Websites.findOne()){
console.log("No websites yet. Creating starter data.");
Websites.insert({
title:"Test site",
url:"http://www.test.com",
description:"This is a test.",
createdOn:new Date(),
totalVotes: 0
});
Websites.insert({
title:"Google",
url:"http://www.google.com",
description:"Popular search engine.",
createdOn:new Date(),
totalVotes: 0
});
}
});
I hope i've been comprehensive and clear with my question. I just want to be able to save up and downvote tallies for each item in my global array but right now there's just a single variable which does nothing for me.
Thanks so much!
you're missing publish and subscribe. the idea is your template will subscribe to the website data, and the server will then publish what you're asking for. you would write a helper that performs a find() on the published data, and an #each loop that would iterate over that data.
once you're done that, the tricky part, which is what you're asking about, is to tag each loop item so that, when clicked, you can uniquely identify it in the event handlers.
let's set up a new template (i'm writing all this code just in this text box, w/o trying it, so please forgive typos):
<template name="Websites">
{{#each website in websites}}
{{website.title}}
Votes: {{website.totalVotes}}
<button class="js-upvote" data-id={{website._id}}>Vote Up</button>
{{/each}}
</template>
then, you need to subscribe, like this:
Template.Websites.onCreated(function() {
this.subscribe('websites');
});
i'll assume you already have a publish written, or auto publish is on...
then write the helper:
Template.Websites.helpers({
websites() {
return Websites.find({});
}
});
and finally the event listener that can identify which item was clicked:
Template.Websites.events({
'click .js-upvote': function(event, template) {
event.preventDefault();
if (event && event.currentTarget && event.currentTarget.dataset) {
let websiteId = event.currentTarget.dataset.id;
// now you can save the incremented votes for this website
}
}
});
Regarding your totalVotes variable, i think i understand what you're trying to do, and now you can get rid of that. with the code i've written, it will save to the db the increments and decrements of each website, and because you're subscribed, you'll get that updated vote total back and reactively display it in the template.
update:
alternatively, accessing _id without writing it to the DOM:
<template name="Websites">
{{#each website in websites}}
{{website.title}}
Votes: {{website.totalVotes}}
<button class="js-upvote">Vote Up</button>
{{/each}}
</template>
Template.Websites.events({
'click .js-upvote': function(event, template) {
event.preventDefault();
let websiteId = this._id;
// now you can save the incremented votes for this website
}
}
});

Getting the value of element clicked on in Emberjs

I have a series of labels that I would like to retrieve the text from when the user clicks on them. I thought the jquery might be doable in Emberjs:
$(this).attr('text');
This however, doesn't work with Ember. My component view is this:
<ul class="list-inline">
{{#each model as |model|}}
<li {{action "sendToInput"}} class="label label-default">{{model.name}}</li>
{{/each}}
</ul>
I would like to retrieve that model.name value via the action "sendToInput".
In my component js file I have tried:
actions: {
sendToInput() {
$(this.target).attr('text');
}
}
I have also tried:
this.innerHTML;
$(this).text();
this.get('text');
$(this).val();
I have also opened up the console and dug through this and cannot seem to find where they store the element clicked on.
The documentation doesn't mention this and there's no issue I can find on the Github for ember-cli.
Thanks.
The common way is to pass a model name as an action parameter like this:
<ul class="list-inline">
{{#each model as |model|}}
<li {{action "sendToInput" model.name}} class="label label-default">{{model.name}}</li>
{{/each}}
</ul>
and then handle it in the action:
actions: {
sendToInput(name) {
// do something with name
}
}
But if you still need to access the exact element's html, there is another workaround:
export default Ember.Component.extend({
didInsertElement () {
this.$().on('click', '.label', function (event) {
$(this).text();
})
}
});

Meteor - display collection with conditions

I have a user based Meteor application with a collection representing groups.
A group is something like this:
{ name: groupname, members: [memberuseridlist], owner: owneruserid}
I have a template for these groups that looks like this:
{{#each groups}}
<li>{{name}}
-
<button class="join">+</button>
<button class="leave">-</button>
<button class="delete">x</button>
</li>
{{/each}}
But I'd like to ensure that only the relevant buttons are displayed e.g.:
{{#each groups}}
<li>{{name}}
-
{{#unless ismember}}<button class="join">+</button>{{/unless}}
{{#if ismember}}<button class="leave">-</button>{{/if}}
{{#if isowner}}<button class="delete">x</button>{{/if}}
</li>
{{/each}}
I have a set of template helper methods but I don't understand how to pass the actual group into the function, so that I can evaluate ismember and isowner for each group.
The context within {{#each groups}} is a group document. So within your helpers you can use this to mean a group. Try something like this:
Template.myTemplate.helpers({
ismember: function() {
return _.contains(this.memberuseridlist, Meteor.userId());
},
isowner: function() {
return this.owner === Meteor.userId();
}
});
If you wish to make these helpers more portable between your templates, see my article on models.

meteor: render a template in a specific context

I have two templates
<body>
{{>tmpl1}}
{{>tmpl2}}
....
</body>
in the tmpl1 I have a list of items, which can be clicked. When one is click, tmpl2 shown the details. How can this be achieved ?
So, just to make the idea clearer, here is how I get the list of items
Template.tmpl1.items = function () {
return Items.find({}).fetch();
};
tmpl1 displays them as follows
<template name="tmpl1">
{{#each items}}
{{title}}
{{/each}}
....
</template>
So tmpl2 template might look like this
<template name="tmpl1">
<h1>{{title}}</h1>
<p>{{content}}</p>
</template>
Any suggestions how to link the selected item in tmpl1 with tmpl2 ?
First, put your templates in a container so that you can manipulate the context. Also, put the details template in a #with context:
<template name="box">
{{> list}}
{{#with item}}
{{> details}}
{{/with}}
</template>
Now, add the event handlers for the box template. Assuming your entries in the list looks like this:
<div class="listItem" data-id="{{_id}}">{{title}}</div>
Write the handler:
Template.box.events({
'click .listItem': function(e, t) {
t.data.itemId = $(e.target).data('id');
t.data.itemDep.changed();
}
});
Finally, create the data helper and dependency for the selected item:
Template.box.created = function() {
this.data.itemDep = new Deps.Dependency();
};
Template.box.item = function() {
this.itemDep.depend();
return Items.findOne(this.itemId);
};

Ember.js property and ArrayController in template

I've got a setup like this in Ember:
App.ListObject = Ember.Object.create({
knownThings: function() {
var ot = this.openThings.get('content');
var ct = this.closedThings.get('content');
var kt = ot.concat(ct);
var known = Ember.ArrayController.create({content: kt});
return known;
}.property(),
openThings: Ember.ArrayController.create({
content: []
}),
closedThings: Ember.ArrayController.create({
content: []
}),
})
Basically, known things is the combined arrays of openThings and closedThings. I can't seem to figure out how to iterate over knownThings in the template. Just doing
{{#each App.ListObject.knownThings }}
Does not work as the property needs to be accessed like App.ListObject.get('knownThings') but that doesn't work in the template unless I'm doing something terribly wrong. Iterating over the other attributes in the template does work (open and closed things)
So, how would you iterate over knownThings in the template?
Slight Modifications needed...
Firstly,
knownThings: function() {
//use get to retrieve properties in ember, Always !
var ot = this.get('openThings').get('content');
//var ot = this.get('openThings.content') if you are using latest ember
var ct = this.get('closedThings').get('content');
//var ot = this.get('closedThings.content') if you are using latest ember
var kt = ot.concat(ct);
var known = Ember.ArrayController.create({content: kt});
return known;
//Add dependencies to keep your knownThings in sync with openThings & closedThings if at all they change in future
}.property('openThings', 'closedThings')
Coming to Handlebars iterate using
//you forgot content property, and in handlebars you don;t need to use get, dot operator is enough
{{#each App.List.knownThings}}
Let me know if this works...
Update
Working Fiddle...
Unless I didn't understand what you're saying, I think you should have ListObject extending Em.ArrayController instead of Em.Object. Also, if your property depends on content, it should be .property('content.#each'). If you're using the router, your template should look like {{#each thing in controller.knownThings}} and you use {{thin.something}}, if not using router, then {{#each item in App.listObject.knownThings}}. Also, openThings and closedThings don't seem to be correct and the way you're accessing them is wrong too.
I didn't write a fiddle for this specific case cause I don't really know what you're trying to do, but take a look at this fiddle, specifically at App.ResourcesController and the template 'resources-view':
Controller:
// ...
App.ResourcesController = Em.ArrayController.extend({
content: [],
categories: ['All', 'Handlebars', 'Ember', 'Ember Data', 'Bootstrap', 'Other'],
categorySelected: 'All',
filtered: function() {
if(this.get('categorySelected') == "All") {
return this.get('content');
} else {
return this.get("content")
.filterProperty(
"category",
this.get('categorySelected')
);
}
}.property('content.#each', 'categorySelected'),
filteredCount: function() {
return this.get('filtered').length;
}.property('content.#each', 'categorySelected'),
hasItems: function() {
return this.get('filtered').length > 0;
}.property('filteredCount')
);
// ...
Template:
<script type="text/x-handlebars" data-template-name="resources-view">
<h1>Ember Resources</h1>
{{#view Bootstrap.Well}}
The following is a list of links to Articles, Blogs, Examples and other types of resources about Ember.js and its eco-system.
{{/view }}
{{view Bootstrap.Pills contentBinding="controller.controllers.resourcesController.categories" selectionBinding="controller.controllers.resourcesController.categorySelected"}}
<i>{{controller.filteredCount}} Item(s) Found</i>
{{#if controller.hasItems}}
<ul>
{{#each resource in controller.filtered}}
<li>
<a {{bindAttr href="resource.url"
target="resource.target"
title="resource.description"}}>
{{resource.htmlText}}
</a>
</li>
{{/each}}
</ul>
{{else}}
{{#view Bootstrap.AlertMessage type="warning"}}
Couldn't find items for {{controller.categorySelected}}
{{/view}}
{{/if}}
</script>

Categories