I am working on arabic-russian dictionary, editable by users. And I faced an Issue, that I don't understand. I'm using blaze. And there is idea of structure:
<ModalForm>
<ArticleForm>
</ModalForm>
<ArticlePage>
<ArticlesList>
<ArticleSingle><ArticleForm>
</ArticlesList>
</ArticlePage>
Modal form opens ArticleForm
for insert new Article
, and then redirects page to a new ArticlePage
.
ArticlesList
wraps couples of <ArticleSingle><ArticleForm>
. One of them is visible, it depend on the editMode
parameter. ArticlePage
passes an array from one article ([articles.findOne]
) to ArticlesList
.
When I add, the first article redirection works fine and a page with a new Article
opens. There is only one ArticlePage
template in use, with one article instance. Redirection works from after-insert-hook (collections-hooks).
But when I repeat it at second time, there is an error, Exception from Tracker recompute function:
The ArticlePage
doesn't render, and in the console I see two ArticlePage
calls, first with real data, and second with undefined
. I can't understand where does this "undefined" article comes from.
If I reload that empty page, it will be rendered well with the new article.
Update:
This error appears only when I’m trying to redirect from /articles/xxxx
to /articles/yyyy
. Redirect from main page /
to /articles/xxxx
works fine even many times.
Here is how it looks.
Code
component handles displaying couples ArticleSingle - ArticleEditForm, for list of Articles (on Search page, on ActiveCorrections page etc), and also on ArticlePage, when it displays only one article, witch is wrapped into array [Article].
ArticlePage.html
<template name="ArticlePage">
<div class="container article-page">
<section class="row">
{{#if Template.subscriptionsReady}}
{{> ArticlesList articles=articles}}
{{else}}
<p>Loading...</p>
{{/if}}
</section>
</div>
</template>
ArticlePage.js
import { Articles } from "/imports/api/articles.js";
Template.ArticlePage.onCreated(function() {
var self = this;
var id = FlowRouter.getParam("id");
self.autorun(function() {
self.subscribe("articleSingle", id);
});
});
Template.ArticlePage.helpers({
articles() {
const id = FlowRouter.getParam("id");
const article = Articles.findOne({ _id: id });
console.log("articlePage article", article);
return [article];
},
isAdmin() {
return Roles.userIsInRole(loggedInUser, ['admin'])
}
});
ArticlesList.html
<template name="ArticlesList">
<div class="articles-list">
{{#each articles}}
<section class="row">
<div class="col-sm-6 -sticky">
<div class="main-article">
{{> ArticleSingle}}
</div>
</div>
<div class="col-sm-6">
{{#if showEditForm _id}}
{{> ArticleForm }}
{{else}}
{{#each corrections }}
<div class="correction">
{{#if showEditForm _id}}
{{> ArticleForm }}
{{else}}
{{> ArticleSingle }}
{{/if}}
</div>
{{/each}}
{{/if}}
</div>
</section>
{{/each}}
</div>
</template>
ArticlesList.js
Template.ArticlesList.onCreated(function() {
Session.set("showEditFormForArticle", "");
});
Template.ArticlesList.helpers({
showEditForm(articleId) {
return articleId == Session.get("showEditFormForArticle");
},
corrections() {
if (this.corrections)
return this.corrections.map(elem => {
return { ...elem, _id: elem._id + "-by-" + elem.editedByUserName };
});
else [];
}
});
Template.ArticlesList.events({
"click .correction .btn-success"() {
let doc_id = this._id.split("-")[0];
Meteor.call("articles.accept_correction", doc_id, this);
},
"click .correction .btn-danger"() {
let doc_id = this._id.split("-")[0];
Meteor.call("articles.reject_correction", doc_id, this);
},
"click .main-article .btn-success"() {
let doc_id = this._id.split("-")[0];
Meteor.call("articles.accept", doc_id, this);
},
"click .main-article .btn-danger"() {
let doc_id = this._id.split("-")[0];
Meteor.call("articles.reject", doc_id, this);
}
});
displays single article of dictionary, with it words, notes, translations and examples.
ArticleSingle.html
<template name="ArticleSingle">
<div id="article-{{_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>
<!-- Глагол 1й породы имеет дополнительную информацию для вывода, поэтому
особый шаблон его вывода, например, среднекорневую глассную и масдары -->
{{#each words}}
<div class="note">{{note}} </div>
<div class="word -arabic-text-big">{{word}}</div>
<div class="collapse transcription">{{transcr word}}</div>
{{#if isMiddleHarakat ../middleHarakat @index}}
<div class="note middleHaracat" title="среднекорневая гласная настоящего времени">
{{../middleHarakat}}
</div>
{{/if}}
{{/each}}
<button type="button" class="btn btn-default btn-xs btn-transcript"
data-toggle="collapse" data-target="#article-{{_id}} .transcription">
Транскрипция
</button>
</div>
</div>
<div class="panel-body">
<ol class="translations">
{{#each translations}}
<li class="translation">
{{translation}}
{{#if examplesCount examples}}
<a href="" class="showExamplesButton" data-toggle="collapse" title="примеры" data-target=".examples-{{../_id}}-{{@index}}">
<small><i class="glyphicon glyphicon-asterisk"></i>
{{examplesCount examples}}</small>
</a>
<ul class="examples collapse examples-{{../_id}}-{{@index}}">
{{#each examples}}
<li>
<div class="example -arabic-text-mid">{{example}}</div>
<div class="translation">{{translation}}</div>
</li>
{{/each}}
</ul>
{{/if}}
</li>
{{/each}}
</ol>
{{#if image.link}}
<div class="image">
<img class="img-responsive img-rounded" src="{{image.link}}" alt="ArticleImage">
</div>
{{/if}}
<!-- TAGS -->
{{#if tagsRoots}}
<div>
Корень: {{> TagsSelectedSynonyms tags=tagsRoots removeButton=false}}
</div>
{{/if}}
{{#if tagsSubjects}}
<div>
Тематики: {{> TagsSelectedSubjects tags=tagsSubjects removeButton=false}}
</div>
{{/if}}
{{#if tagsSynonyms}}
<div>
Синонимы: {{> TagsSelectedSynonyms tags=tagsSynonyms removeButton=false}}
</div>
{{/if}}
</div>
<div class="panel-footer">
{{> ArticleMenu }}
{{> ArticleCorrectionMenu}}
{{#if showApproveButtons}}
{{> ApproveButtons}}
{{/if}}
</div>
</div>
</template>
ArticleSingle.js
import { Articles } from "/imports/api/articles.js";
import { Subjects } from "/imports/api/subjects.js";
import { transcription, isNotDiacritic } from "/imports/transcription.js";
Template.ArticleSingle.onCreated(function() {
Meteor.subscribe("subjects");
Meteor.subscribe("articlesByIds", this.data.synonyms);
Meteor.subscribe("articlesByIds", this.data.roots);
});
Template.ArticleSingle.helpers({
showApproveButtons() {
return (
Roles.userIsInRole(loggedInUser, ['admin']) &&
this.published === false &&
this.deleted !== true
);
},
notPublished() {
return this.published === false;
},
isMiddleHarakat(middleHarakat, index) {
return middleHarakat && index == 0;
},
rootArticle() {
Meteor.subscribe("articleSingle", this.rootId);
return Articles.findOne({ _id: this.rootId });
},
image() {
const image = Images.findOne({ _id: this.picture });
return image;
},
imageJSON() {
const image = Images.findOne({ _id: this.picture });
image0 = JSON.stringify(image.fetch());
return image0;
},
examplesCount: function(examples) {
return examples.length || 0;
},
transcr: function(text) {
if (text.trim()) return "[ " + transcription(text) + " ]";
},
tagsSubjects() {
const ids = this.subjects;
const tags = [];
let tagsUnordered = Subjects.find({ _id: { $in: ids } }).fetch(); // эта шляпа возвращает массив в смешанном порядке, поэтому их надо заново упорядочить
ids.forEach(tagId => {
tags.push(
tagsUnordered.filter(elem => {
return elem._id == tagId;
})[0]
);
});
return tags;
},
tagsSynonyms() {
const ids = this.synonyms;
const tags = [];
let tagsUnordered = Articles.find({ _id: { $in: ids } }).fetch();
// эта шляпа { $in: ids } возвращает массив в смешанном порядке, поэтому их надо заново упорядочить
ids.forEach(tagId => {
tags.push(
tagsUnordered.filter(elem => {
return elem._id == tagId;
})[0]
);
});
return tags;
},
tagsRoots() {
const ids = this.roots;
const tags = [];
let tagsUnordered = Articles.find({ _id: { $in: ids } }).fetch();
// эта шляпа { $in: ids } возвращает массив в смешанном порядке, поэтому их надо заново упорядочить
ids.forEach(tagId => {
tags.push(
tagsUnordered.filter(elem => {
return elem._id == tagId;
})[0]
);
});
return tags;
}
});
And what is strange, when I make in Template.ArticleSingle.onCreated
console.log(this), when I add article from main page /
, after redirection there is only one TemplateInstance, and it renders well.
But when I create article y
from article/x
page, after redirection from existing page article/x
to new created article/y
page, in console there is 3 TemplateInstances, third of them undefined
. I still don't understand from where it appears :(