Неожиданная статья = 'undefined' появляется и ломает мое приложение (исключение из функции пересчета Tracker:)
Я работаю над арабско-русским словарем, редактируемым пользователями. И я столкнулся с проблемой, которую я не понимаю. Я использую Blaze. И есть идея структуры:
<ModalForm>
<ArticleForm>
</ModalForm>
<ArticlePage>
<ArticlesList>
<ArticleSingle><ArticleForm>
</ArticlesList>
</ArticlePage>
Модальная форма открывается ArticleForm
для вставки нового Article
, а затем перенаправляет страницу на новый ArticlePage
,
ArticlesList
оборачивает пары <ArticleSingle><ArticleForm>
, Один из них виден, это зависит от editMode
параметр. ArticlePage
пропускает массив из одной статьи ([articles.findOne]
) чтобы ArticlesList
,
Когда я добавляю, перенаправление первой статьи работает нормально и страница с новым Article
открывается. Здесь только один ArticlePage
шаблон используется с одним экземпляром статьи. Перенаправление работает от after-insert-hook (collection-hooks).
Но когда я повторяю это во второй раз, возникает ошибка, Exception from Tracker recompute function:
ArticlePage
не рендер, и в консоли я вижу два ArticlePage
звонки, сначала с реальными данными, а затем с undefined
, Я не могу понять, откуда эта "неопределенная" статья.
Если я перезагрузлю эту пустую страницу, она будет хорошо отображаться в новой статье.
Обновить:
Эта ошибка появляется только при попытке перенаправить с /articles/xxxx
в /articles/yyyy
, Перенаправление с главной страницы /
в /articles/xxxx
отлично работает даже много раз.
Вот как это выглядит.
Код
компонент обрабатывает отображение пар ArticleSingle - ArticleEditForm, для списка статей (на странице поиска, на странице ActiveCorrections и т. д.), а также для ArticlePage, когда он отображает только одну статью, ведьма помещается в массив [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);
}
});
отображает отдельную статью словаря, с ней слова, заметки, переводы и примеры.
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;
}
});
И что странно, когда я зарабатываю в Template.ArticleSingle.onCreated
console.log(это), когда я добавляю статью с главной страницы /
, после перенаправления есть только один TemplateInstance, и он хорошо рендерится. Но когда я создаю статью y
от article/x
страница, после перенаправления с существующей страницы article/x
к новому созданному article/y
страница, в консоли есть 3 TemplateInstances, треть из них undefined
, Я до сих пор не понимаю, откуда это появляется:(
1 ответ
Наконец я нашел ответ (с помощью добрых людей):
ArticlePage.js
:
Template.ArticlePage.onCreated(function() {
var self = this;
self.autorun(function() {
const id = FlowRouter.getParam("id");
if (id) {
self.subscribe("articleSingle", id);
}
});
});
При повторном рендеринге после перенаправления FlowRouter.getParam("id")
было undefined
и у нас нет подписки. Теперь все работает хорошо.