Meteor template not updating on changed data - javascript

I'm currently building a nifty little 'talent point distributor' view, similar to what popular RPG games offer. I didn't want a huge wall of HTML code for all the buttons and textboxes, so I created a template to which I pass two parameters:
the name of the stat I want to alter
the initial value of the stat
The template renders correctly, and I notice that when I log the results to the console, the variable seems to be changed correctly. However, the displayed value does not change and will always stay at 0.
Here is the template itself:
<template name="attributeStepper">
<div class="row" style="margin: 1em;">
<div class="col-sm-3 col-md-2">
<h4>{{toUpper attribute}}</h4>
</div>
<div class="col-sm-6 col-md-4">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default btn-value-dec">
<span class="glyphicon glyphicon-chevron-down"></span>
</button>
<button type="button" class="btn btn-default disabled">{{attributeValue}}</button>
<button type="button" class="btn btn-default btn-value-inc">
<span class="glyphicon glyphicon-chevron-up"></span>
</button>
</div>
</div>
</div>
</template>
Here is the helper I defined for the template:
Template.attributeStepper.helpers({
toUpper : function(str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
})
Template.attributeStepper.events({
'click .btn-value-inc' : function(event, tmpl) {
tmpl.data.attributeValue ++;
},
'click .btn-value-dec' : function(event, tmpl) {
tmpl.data.attributeValue --;
}
});
And this is how I call the templates from the actual view:
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Attributes</h3>
</div>
{{ >attributeStepper attribute="strength" attributeValue="0"}}
{{ >attributeStepper attribute="courage" attributeValue="0"}}
{{ >attributeStepper attribute="intelligence" attributeValue="0"}}
{{ >attributeStepper attribute="agility" attributeValue="0"}}
{{ >attributeStepper attribute="dexterity" attributeValue="0"}}
{{ >attributeStepper attribute="intuition" attributeValue="0"}}
{{ >attributeStepper attribute="charisma" attributeValue="0"}}
</div>
I hope you can make any sense out of this and tell me what I'm doing wrong, because I feel like I'm not following the mindset behind Meteor correctly yet.
Cheers!

There is nothing wrong but also nothing reactive in your code. For the attributeValue you should use a template based ReactiveVar which is created at the onCreate Event
Template.attributeStepper.onCreated(function() {
if (! _.isUndefined(this.data.startingValue))
this.attributeValue = new ReactiveVar(Number(this.data.startingValue));
else
this.attributeValue = new ReactiveVar(0);
})
You can use some initialValue from Template as you like
See complete example at the MeteorPad I created for you.
http://meteorpad.com/pad/Zw7YnnW57uuGKcu3Q/MultipleTemplateUsage
This should solve your question
Cheers
Tom

Do you have idea about reactive-var in meteor (Meteor Doc) or you can also use Session instead of reactive-var (ReactiveVar is similar to a Session variable)
Have a look at changes as per your code.
Here is the template(.html)
<template name="attributeStepper">
<div class="row" style="margin: 1em;">
<div class="col-sm-3 col-md-2">
<h4>{{toUpper attribute}}</h4>
</div>
<div class="col-sm-6 col-md-4">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default btn-value-dec">
<span class="glyphicon glyphicon-chevron-down"></span>
</button>
<button type="button" class="btn btn-default disabled">{{getAttributeValue}}</button>
<button type="button" class="btn btn-default btn-value-inc">
<span class="glyphicon glyphicon-chevron-up"></span>
</button>
</div>
</div>
</div>
</template>
Here is helpers for your template(.js)
Template.attributeStepper.created = function(){
this.attributeValue = new ReactiveVar(parseInt(this.data.attributeValue));
}
Template.attributeStepper.helpers({
toUpper : function(str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
},
getAttributeValue : function(){
return Template.instance().attributeValue.get();
}
});
Template.attributeStepper.events({
'click .btn-value-inc' : function(event, tmpl) {
tmpl.attributeValue.set(tmpl.attributeValue.get()+1)
},
'click .btn-value-dec' : function(event, tmpl) {
tmpl.attributeValue.set(tmpl.attributeValue.get()-1)
}
});
Template.attributeStepper.created = function(){...} method called before your template's logic is evaluated for the first time.

Related

Where does template.data of Blaze form go after first submit?

I use {{> ArticleForm}} for create and update Articles in arabic-russian dictionary. I use blaze and ReactiveDict in my templates, after some problems with Autoform plugin.
First I was using it for update Article and everything works good. Now I added same form for create Article. I placed {{ > ArticleForm}} to {{> Header}} component, inside bootstrap modal, and call it by "+Add" button in top menu. It opens my form. First insert of Article works good. In DB there is new article, and even I redirected to new article. But at second call, template.data is empty and form is not interactive, it fires errors, because after update any field, ArticleForm should write ReactiveDict from template.data, but it doesn't exist. I can't understand, where goes template.data after firs successful insertion.... Please answer if someone knows...
Here is how it looks:
Here is data context of my form :
Template.ArticleForm.helpers({
article() {
console.log("helper-article");
if (!this.words) this.words = [{ note: "", word: "" }];
if (!this.translations) this.translations = [{ translation: "" }];
if (!this.synonyms) this.synonyms = [];
if (!this.roots) this.roots = [];
if (!this.subjects) this.subjects = [];
//newWords, newTranslations - это добавление имен к элементам формы,
//по которым можно будет отслеживать все изменения в форме
let newWords = this.words.map((elem, index) => {
return { note: elem.note, word: elem.word, wordId: `words.${index}` };
});
let newTranslations = this.translations
? this.translations.map((elem, index) => {
return {
translation: elem.translation,
translationId: `translations.${index}.translation`,
examples: elem.examples
? elem.examples.map((elem2, index2) => {
return {
example: elem2.example,
translation: elem2.translation,
exampleId: `translations.${index}.examples.${index2}`
};
})
: []
};
})
: [];
this.words = newWords;
this.translations = newTranslations;
this.picture = Session.get("picture");
Template.instance().reactiveForm.set("article", this);
const article = Template.instance().reactiveForm.get("article");
return article;
},
deleted() {
return this.deleted ? "checked" : "";
},
showMiddleHarakat(speachPart, index) {
return speachPart == "глагол, I порода" && index == 0;
},
picture() {
return Session.get("picture");
}
});
Here is my template {{> ArticleForm}}
<template name="ArticleForm">
<form id="articleForm-{{_id}}" class="panel panel-default article {{#if notPublished}}-opacity-06{{/if}}">
<div class="panel-heading">
<div class="panel-title words">
<div class="label label-info speach-part">{{speachPart}}</div><br />
<!-- Глагол 1й породы имеет дополнительную информацию для вывода, поэтому
особый шаблон его вывода, например, среднекорневую глассную и масдары -->
{{#each article.words}}
<div class="wordEdit editField" id="{{wordId}}">
<i class="glyphicon glyphicon-remove remove-word -remove" id="remove.{{wordId}}"></i>
<input type="text" placeholder="прим." value="{{note}}" name="{{wordId}}.note" class="form-control note">
<input type="text" placeholder="слово" value="{{word}}" name="{{wordId}}.word" class="form-control word -arabic-text-mid">
</div>
{{#if showMiddleHarakat ../speachPart #index}}
<div class="note middleHarakat" title="среднекорневая гласная настоящего времени">
<input type="text" placeholder="скгнв" value="{{../middleHarakat}}" name="middleHarakat" class="form-control note">
</div>
{{/if}}
{{/each}}
<div class="add-word">
<i class="glyphicon glyphicon-plus"></i>
</div>
</div>
</div>
<div class="panel-body">
<div class="translations">
{{#each article.translations}}
<div class="translation">
<div class="editField editTranslation" id="{{translationId}}">
<input type="text" name="{{translationId}}" value="{{translation}}" class="form-control" placeholder="перевод">
<i class="glyphicon glyphicon-remove remove-translation -remove" id="remove.{{translationId}}"></i>
</div>
<div class="examples examples-form-{{../_id}}-{{#index}}">
{{#each examples}}
<div class="exampleEdit editField" id="{{exampleId}}">
<input type="text" placeholder="пример" value="{{example}}" name="{{exampleId}}.example" class="form-control example -arabic-text-mid">
<input type="text" placeholder="перевод примера" value="{{translation}}" name="{{exampleId}}.translation" class="form-control translation">
<i class="glyphicon glyphicon-remove remove-example -remove" id="remove.{{exampleId}}"></i>
</div>
{{/each}}
<button class="btn btn-default btn-xs add-example" id="addExampleFor.{{translationId}}">
<i class="glyphicon glyphicon-plus"></i>Пример
</button>
</div>
</div>
{{/each}}
<button class="btn btn-default btn-sm add-translation">
<i class="glyphicon glyphicon-plus"></i>Перевод
</button>
</div>
{{> TagsSubjects}}
{{> TagsSynonyms}}
{{> TagsRoots}}
<!--
<div class="uploadImage">
{{> uploadForm}}
picture: {{picture}}
</div>
-->
</div>
<div class="panel-footer">
<button class="btn btn-primary article-save">Сохранить</button>
<button class="btn btn-default article-edit-cancel">Отмена</button>
</div>
</form>
</template>
This could be because you are not clearing the form template properly when the modal closes? Remember the modal is just hidden, the template is not removed.
I came across a similar issue when adding AutoForm support to my boostrap modal package.

Expression in ng-init not working with ng-repeat

So I am trying to have many divs with background images and disable the background image when I hover over any particular div.
<div id="interest" class="col-md-4 col-sm-6" ng-repeat="interest in vm.interestList"
ng-init="myStyle={'background-image': 'url(interest.src)'}"
ng-style="myStyle" ng-mouseenter="myStyle={}"
ng-mouseleave="myStyle={'background-image': 'url(interest.src)'}">
<button id="interestButton" class="btn btn-default btn-lg" >{{interest.interestName}}</button>
</div>
For some reason the myStyle in ng-init never gets the actual value in interest.src rather just gets 'interest.src'. I also tried {{interest.src}} but that does not work.
Any help is appreciated!!
Try this
var jimApp = angular.module("mainApp", []);
jimApp.controller('mainCtrl', function($scope){
$scope.interestList = [{interestName:"One", src:"img/one.png"}];
$scope.url = function(url){
return 'url('+url+')';
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="mainApp" ng-controller="mainCtrl">
<div id="interest" class="col-md-4 col-sm-6" ng-repeat="interest in interestList"
ng-init="myStyle = {'background-image': url(interest.src)}"
ng-style="myStyle" ng-mouseenter="myStyle={}"
ng-mouseleave="myStyle={'background-image': url(interest.src)}">
<button id="interestButton" class="btn btn-default btn-lg" >{{interest.interestName}}</button>
</div>
</div>
You will have to interpolate the string literal of your evaluated angular expressions for interest.src
Instead of
ng-init="myStyle={'background-image': 'url(interest.src)'}" change it to ng-init="myStyle={'background-image': 'url(' + interest.src + ')'}"
Note the string concantenation via +
Here is the plnkr
try like this
<div id="interest" class="col-md-4 col-sm-6" ng-repeat="interest in vm.interestList"
ng-style="{'background-image': 'url('+interest.src+')'}" ng-mouseenter="{}"
ng-mouseleave="{'background-image': 'url('+interest.src+')'}">
<button id="interestButton" class="btn btn-default btn-lg" >{{interest.interestName}}</button>
</div>

How to access current row data in angular-datatables using templates?

I am using angular-datatables. There is a column Actions which I render using an inline template. I want to access current row data in that template. How do that?
controller
$scope.dtOptions = DTOptionsBuilder.newOptions().withOption('ajax', {
url: '/api/department',
type: 'GET'
})
.withDataProp('data')
.withOption('processing', true)
.withOption('serverSide', true)
.withPaginationType('full_numbers')
.withOption('createdRow', function (row, data, dataIndex) {
return $timeout(function() {
// Recompiling so we can bind Angular directive to the DT
return $scope.$apply($compile(angular.element(row).contents())($scope));
});
})
.withBootstrap();
$scope.dtColumns = [
DTColumnBuilder.newColumn('id').withTitle('ID'),
DTColumnBuilder.newColumn('name').withTitle('Name'),
DTColumnBuilder.newColumn('actions').withTitle('Actions').withOption("searchable", false)
];
view
<div class="hbox hbox-auto-xs hbox-auto-sm" ng-controller="DepartmentsController">
<!-- Inline Template -->
<script type="text/ng-template" id="actions.html">
<button class="btn btn-primary btn-xs"
ng-click="edit(/** CURRENT ROW ELEMENT ID */)"><i class="fa fa-edit"></i> Edit</button>
<button class="btn btn-danger btn-xs"
ng-click="delete()"><i class="fa fa-trash"></i> Delete</button>
</script>
<div class="bg-light lter b-b wrapper-md">
<h1 class="m-n font-thin h3">Departments</h1>
</div>
<div class="wrapper-md">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-xs-6">
<button class="btn m-b-md btn-md btn-primary " ui-sref="manager.departments.create">
<i class="fa fa-plus"></i> <span class="hidden-sm hidden-xs">Add Department</span></button>
</div>
</div>
<div class="row">
<div class="col-sm-12 m-b-xs">
<table datatable="" dt-options="dtOptions" dt-columns="dtColumns" class="table table-striped b-t b-b">
<thead>
<tr>
<th style="width:20%">ID</th>
<th style="width:60%">Name</th>
<th style="width:20%">Actions</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
Here is plunkr to help you: http://plnkr.co/edit/iAZBof7g6cp68RnM0X8H?p=preview
After hours of struggle, I found the solution. It's quite obvious when you see it.
I created a new scope and added data to it before passing it to $compile in createRow callback. Creating a new scope is necessary to pass unique data to each row. If you simply passed by $scope.row then each row will have the same row equal to the last row processed.
controller
.withOption('createdRow', function (row, data, dataIndex) {
// Create a new scope for each row, otherwise, data will
// not be unique for each row becuase of data bindings
var $newScope = $scope.$new(true);
$newScope.row = data;
// Pass any methods you are using in current scope
$newScope.delete = $scope.delete;
return $timeout(function() {
// Recompiling so we can bind Angular directive to the DT
return $scope.$apply($compile(angular.element(row).contents())($newScope));
});
});
view
<script type="text/ng-template" id="actions.html">
<button class="btn btn-primary btn-xs" ui-sref="manager.departments.edit({id: {{ row.id }} } )"><i class="fa fa-edit"></i> Edit</button>
<button class="btn btn-danger btn-xs" ng-bootbox-confirm="Are you sure you want to delete this department?" ng-bootbox-confirm-action="delete(row.id)"><i class="fa fa-trash"></i> Delete</button>
</script>
I used the above accepted answer, and it worked fine. However, later when we moved to production and the rows per page changed from 20 to 500, I saw significant performance issues through chrome developer tools (Most time spent on hundreds of setTimer and listener events)
I found the official document here which gives us an example as below:
.withOption('createdRow', createdRow);
// ...
function createdRow(row, data, dataIndex) {
// Recompiling so we can bind Angular directive to the DT
$compile(angular.element(row).contents())($scope);
}
This piece of code does not use the $timeout or $apply functions, but still works well. If you run into performance issues as I did, this may help.

show element in template if owner meteor js

I am writing a messaging app which has a delete / edit function for a user for a given message that is submitted. What I would like to do is write something like:
{{#if currentUser._id === this._id}}
<!-- Show -->
{{/if}}
But this is probably wrong I have a template written for the message record:
<template name="message">
<div class="row message-row">
<div class="col-md-12">
<div class="message-container">
<div class="message-avatar">
<img src="{{userAvatar}}">
</div>
<p>{{message}}</p>
<div class="message-time">{{prettifyDate time}}</div>
<!-- this is the div to hide / show based on the conditional -->
<div class="message-controls">
<button class="btn btn-link btn-xs" type="button" id="deleteMessage"><i class="fa fa-trash-o"></i></button>
<button class="btn btn-link btn-xs" type="button" id="editMessage"><i class="fa fa-edit"></i></button>
</div>
<!-- end -->
</div>
</div>
</div>
</template>
And I am using the following in my client.js
Template.messages.messages = function() {
return Messages.find({}, { sort: {time: -1} });
}
But at this point I am stuck
Assuming your message documents have a userId field, you can simply do this:
Template.message.helpers({
isOwner: function() {
return this.userId === Meteor.userId();
}
});
and:
{{#if isOwner}}
<!-- controls -->
{{/if}}
You could also make a more flexible, reusable global helper for this:
Template.registerHelper('isCurrentUser', function(userId) {
return userId === Meteor.userId();
});
<!-- in this example, the document might have an `ownerId` field rather than `userId` -->
{{#if isCurrentUser ownerId}}{{/if}}
Of course, you also need to validate the updates on the server with the allow/deny API or with custom methods.

ng-repeat nested inside Popover not working

I have the following ng-repeat list:
<div class="row msf-row"
ng-repeat="record in recordlist
people-popover>
<div class="col-md-1 msf-centered" ng-show="editItem == false" ng-hide="editItem">
<button class="btn btn-primary form-control msf-paxlist"
rel="popover"
data-content="<li ng-repeat='passanger in record.pax'>
{{passanger.name}}
</li>"
data-original-title="Passanger List">
{{record.pax.length}} <i class="fa fa-user"></i>
</button>
</div>
</div>
I initialize the popover with a directive:
.directive('peoplePopover', function() {
return function(scope, element, attrs) {
element.find("button[rel=popover]").popover({ placement: 'bottom', html: 'true'});
};
})
The problem is the <li ng-repeat="pasanger in record.pax">{{pasanger.name}}</li>, which will not show.
If I use <li ng-repeat="record.pax">{{pax}}</li>, it will display the array, but if I try to list the objects of the array with ng-repeat, it won't work.
This is how the array (record) looks like:
record in recordlist {
date : "02/12/2014"
time : "00.02.01"
car : "369"
pax: [
{
name : "Ben"
chosen : true
},
{
name : "Eric"
chosen : true
}
]
}
Any tips?
I ran into similar problem and I solved it this way.
I wrapped the ng-repeat block in another directive and pass the collection to that external directive.
div class="row msf-row"
ng-repeat="record in recordlist
people-popover>
<div class="col-md-1 msf-centered" ng-show="editItem == false" ng-hide="editItem">
<button class="btn btn-primary form-control msf-paxlist"
rel="popover"
data-content="<new-directive records=record.pax></new-directive>"
data-original-title="Passanger List">
{{record.pax.length}} <i class="fa fa-user"></i>
</button>
</div>
</div>
Inside the new directive, you will have this code.
<li ng-repeat='passanger in record.pax'>
{{passanger.name}}
</li>
Hopefully this helps someone who runs into similar problem.

Categories

Resources