In the Meteor docs it says that Meteor.startup will be called after the DOM and all templates have been processed. However, my code within Meteor.startup is acting as if the DOM elements aren't there.
In .js:
Meteor.startup(function () {
console.log($('.draggable').length);
});
In .html:
<template name="item">
<div class="draggable ui-widget-content">
</div>
</template>
In the console I see:
0
But on the page I can see my items. And indeed if I include my JQuery in Template.item.rendered or in a mouseover event, I get the correct length of the array. So why would the startup function not have my DOM elements ready to use?
I'm guessing your code looks something like this, but let me know if I'm wrong:
<template name="list">
{{#each items}}
{{> item}}
{{/each}}
</template>
The {{#each ...}} helper works with a cursor object to respond to data changes on the cursor. So, in your case, if that data is coming from the server (e.g. a subscription), at the time of Meteor.startup, the data might not have been loaded yet. So initially your list will be empty. Then, as data comes off the wire a new item template will be rendered for each data item. If you want to make a specific item draggable, you could put that jQuery code in the Template.item.rendered callback.
Does this help?
Related
I have this dom-repeat template in polymer
<template is="dom-if" if="true">
<typeahead items="{{item.children}}"></typeahead>
</template>
<template is="dom-repeat" id="level" items="{{item.children}}">
<li on-click="itemClickHandler" id$="[[getItemId(item)]]" class$="{{isItemSelected(item, selectedItem)}}" >
<template is="dom-if" if="[[!toShowColumnTypeahead()]]"><span>{{getItemLabel(item)}}</span></template>
<template is="dom-if" if="[[toShowColumnTypeahead()]]">
<template is="dom-if" if="[[!item.searchTerm]]" restamp="true">
<span class="f">{{getItemLabel(item)}}</span>
</template>
<template is="dom-if" if="{{item.searchTerm}}" restamp="true">
<span class="">{{item.currentSearchLabel.prefix}}<span class="bold">{{item.currentSearchLabel.highlight}}</span>{{item.currentSearchLabel.suffix}}</span>
</template>
</template>
<button class$="[[getItemOpenerClass(item)]]" on-click="openClickHandler" key$="[[getItemId(item)]]">Open</button>
<template is="dom-if" if="{{_hasChildren(item, showChevron)}}">
<span class="chevron"><i class="fa fa-angle-right"></i></span>
</template>
</li>
</template>
The data for item.children is changing from typeahead polymer component and its notifying it. however if the items.children is modified and was still part of previous result then it is not redrawn on dom, I want it to be redrawn on DOM every time there is any change in the list irrespective of whether it was part of previous change or not. Right now Polymer only redraws the changed elements on the DOM. I tried so many thing from net from this.set to manually calling the render method by query selecting the template.
I'm not entirely sure I follow you 100% but you could try using restamp on the dom-repeat, forcing it to destroy and recreate elements every time item.children is changed.
Ignore this, restamp actually only works on dom-if
Render will synchronously force rendering the changes that will usually be done asynchronously, but it only picks up changes made through the Polymer API, so you might want to make sure the changes made to your item.children are made through the Polymer API and not just native array function (i.e the thing setting it should do something along the line of this.push('theArray', newItem) instead of the usual theArray.push(newItem)).
Edit: to further clarify:
calling render "just" forces to re render the changes synchronously, but this shouldn't force rendering more stuff than the usual asynchronous rendering
your issue probably comes from how the array is updated. Use this.splice, this.push, this.pop, this.unshift or this.shift to mutate your array. Link to the doc
I have an ember application which works fine. But user's interaction does some DOM insertion like below...
$(.test).append(<a {{action "getData"}}>Get Data</a>);
The problem is that Ember seems do not recognize that an action "getData" has been added to the DOM. Nothing is happening when I click the element. Any thoughts on this?
Another way I am trying to do is:
//create the element
$(.test).append(<a id="idnum">Get Data</a>);
//make click listener
$('#idnum').click(function(){
console.log("get data");
}
my question is where should i place the code inside the component so the it can listen on the click event. Thanks.
You should do it in Ember way. Try handlebars {{#if}} helper to render an element dynamically.
{{#if canGetData}}
<a {{action "getData"}}>Get Data</a>
{{/if}}
Here you can set the value of the canGetData to true in the controller based on the users action.
The first example can't work because ember does not analythe the Handlebars elements in the DOM, but rather parses your Handlebars template with HTMLBars, which is a full HTML parser, and then renders it manually by inserting elements, not text into the DOM.
However the second example is the way to go if you have to rely on external code that does manual DOM manipulation. And it does work. Checkout this twiddle.
This does work:
this.$('.target').append('<a id="idnum">Get Data</a>');
this.$('#idnum').on('click', () => {
alert('clicked');
});
Just make sure that the DOM is ready. So do it in the didInsertElement hook or after the user clicked a button or so.
Like Lux suggested avoid DOM manipulation. I prefer the following approach,
if it is dynamic then you can consider wrapping DOM element as a new component and use component helper.
find sample twiddle
In application.js
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
linksArray:[ Ember.Object.create({value:'Text to display',routename:'home'}),
Ember.Object.create({value:'Text to display2',routename:'home'})],
actions:{
addItem(){
this.get('linksArray').pushObject(Ember.Object.create({value:'AddedDynamically',routename:'home'}));
}
}
});
in Application.hbs
<h1>Welcome to {{appName}}</h1>
<br>
{{#each linksArray as |item|}}
{{component 'link-to' item.value item.route }}
{{/each}}
<button {{action 'addItem'}}>Add Item</button>
<br>
{{outlet}}
<br>
<br>
Using Polymer, I am attempting to instantiate several ajax-service elements using template binding, <template repeat=...>.
Code is as follows:
<template repeat="{{viewName, i in views}}">
<section hash={{viewName}} layout vertical center-center on-tap={{closeOpenDrawer}}>
<core-ajax id="ajaxService" auto response={{list}}" url="../componentsItems/demo-components.json"></core-ajax>
<template repeat="{{element, j in list}}">
<workspace-elem class="dropped" name="{{element.name}}"></workspace-elem>
</template>
</section>
</template>
The problem is, each ajax response is concatenated to a shared list variable, rather then instantiating its own local list variable per repeated template, so when the sub template is triggered, it generates <workspace-elem>s in each section for the sum of data from all ajax calls.
Is there an easy way to solve this? Is there something I am over looking?
EDIT:
Same sort of problem occurs with the inner template. Each instantiated inner template shares the list variable, if anything is pushed to template.model.list, all instantiated template models are updated.
When you use response={{list}} in the template, you are not creating that variable, but you are binding the value of the response attribute to an existing variable called ‘list’.
The question you need to ask yourself is: ‘Where does the “list” variable that I'm binding to come from?’ It is not obvious from the snippet you mention, but it's very likely that it's coming from some enclosing custom element, so it's only natural that it will be shared between the iterations of the template (though I'm surprised that it get concatenated instead of overwritten…). I think a good solution would be to encapsulate the code you have in the outer template in a custom element to hold the variable:
<polymer-element name="my-element" attributes="viewName">
<template>
<!--Your original code starts here-->
<section hash={{viewName}} layout vertical center-center>
<core-ajax id="ajaxService" auto response={{list}}" url="../componentsItems/demo-components.json"></core-ajax>
<template repeat="{{element, j in list}}">
<workspace-elem class="dropped" name="{{element.name}}"></workspace-elem>
</template>
</section>
<!--Your original code ends here-->
</template>
<script>
Polymer({
publish: {
list: null
},
created: function() {
// Create your array on instantiation of an element
this.list = [];
}
}
</script>
</polymer-element>
<template repeat="{{viewName, i in views}}">
<my-element on-tap={{closeOpenDrawer}}></my-element>
</template>
I think that should solve your problem. Alternatively, I think it might help to make the outer template an auto-binding template(‘is="auto-binding"’). Then the model of the template would be the template itself, but since I have not used this facility very often, I'm not quite sure (it might be that you're then loosing the ability to bind to ‘views’).
My app displays a collection of items and I'd like to add an item drilldown view.
I've only been able to get half of the solution working; I find the appropriate document and use that document to render the template:
var item = Items.findOne('my_id');
$('#main').html(
Meteor.render(function () {
return Templates.item(item)
}));
This renders the individual item successfully and the appropriate events are bound.
Here's the rub, the template isn't reactive! If I change its data using the associated event handlers or from the console, the template isn't updated. However, a page refresh will reveal the updated data.
I'm a Meteor noobie, so it's probably something very simple. Any help would be greatly appreciated.
It seems to me that you aren't using the templates in they way they were really intended to be.
A meteor app starts with the main html markup which can only exist once in your app..
<head>
<title>My New Fancy App</title>
</head>
<body>
{{>templateName}}
</body>
Then you add a template..
<template name="templateName">
{{#each items}}
template or relevant html goes here..
{{/each}}
</template>
Now you need a template helper to give you data for your {{#each items}} block helper..
Template.templateName.helpers({
items: function(){ return Items.find({}) }
});
All this gets defined on the client side..
Then you'll need a collection and the collection should be defined on both the client and server.
Items = new Meteor.Collection('items');
This should now work as long as you have records in your collection.
Since you wish to only wish to render a single document you can change the helper and template just slightly..
first the helper becomes:
Template.templateName.helpers({
item: function(){ return Items.findOne() }
});
Then the template can reference the values of the returned document through document, so we change our template to:
<template name="templateName">
{{item.propertyName}}
</template>
I've got a few templates which list items (log lines, users) and style them accordingly in tables. Now I've added a "search" page which searches for a string in different item types and displays them on one page. In terms of layout, I want to reuse the original templates, but obviously with the data returned by the search.
How can I do that without duplicating the template itself in the HTML file?
Put another way: How can I use the actual data from the top-level template in the sub-templates.
Small example:
<template name="user_list">
{{#each user_list}}
</template>
<template name="users">
{{> user_list}}
</template>
<template name="search">
{{> user_list}}
{{> group_list}}
</template>
In .coffee:
Template.users.user_list = ->
[a,b,c,d]
Template.search.user_list = ->
[b,c]
Maybe this is an easy one, which would show how little I really understood about Meteor.
Just put the template you want to use inside the {{#each}} statement. Then each item in the array will be the context for that inner template.
HTML file
<template name="users">
<ul>
{{#each UserArr}}
{{> userItem}}
{{/each}}
</ul>
</template>
<template name="userItem">
<li>{{username}} {{profile.name}}</li>
</template>
JS file
Template.users.UserArr = function()
{
return Meteor.users.find({});
};
another solution is to put your request in a Session attribute and check for its existence when querying for the users. By default Session.get("userQuery") would return undefined but once you enter something in the search field you change that with Session.set("userQuery", {type: "something"})
Now in the coffeescript you could say:
Template.users.user_list = ->
if(Session.get("userQuery"))
[a,b,c,d]
else
[b,c]
or alternatively use a MiniMongo query because it is much nicer :-)
the nice thing is that your HTML will rerender because it is reactive to the Session's content.