1

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:

image|690x111

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. error%20of%20redirect|690x353

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 :(

Community
  • 1
  • 1
Rustam Apay
  • 551
  • 1
  • 4
  • 18
  • 1
    Try to figure out where you access the `synonyms` attribute in your template helpers code (most likely) and add a check for the object's existence before it, as the computation may be fun multiple times, some of which with `undefined`. You may also want to pause the debugger on error to see exactly where and when this happens. – MasterAM Apr 10 '18 at 12:52
  • @MasterAM, i tried to comment all things like article.synonyms, but there is still appears 3 articles while redirection between `article/x` to 'article/y`: first with passed data from form, second with new created article, and third "undefined". When redirection from main page to `article/x`, then only 1 article and it renders well. – Rustam Apay Apr 10 '18 at 14:34
  • 2
    I am not sure I follow you precisely, but still, you need to guard against the possibility of it being `undefined` until the data is available, which is fairly easy (e.g, an `if` statement will do). If you are not sure what to do, edit your question to include the relevant code. – MasterAM Apr 10 '18 at 17:23
  • @MasterAM, thank you for attention to my Issue. I've added code examples to the question. – Rustam Apay Apr 11 '18 at 08:46

1 Answers1

0

Finally I've found the answer (with good people's help):

ArticlePage.js:

Template.ArticlePage.onCreated(function() {
  var self = this;
  self.autorun(function() {
    const id = FlowRouter.getParam("id");
    if (id) {
      self.subscribe("articleSingle", id);
    }
  });
});

While re-rendering after redirection FlowRouter.getParam("id") was undefined and we haven't subscription. Now all works well.

Rustam Apay
  • 551
  • 1
  • 4
  • 18