简体   繁体   中英

Meteor.js: how to delay Meteor.call() until have all data? Is this a scoping issue or an async issue?

I have a Meteor app that I'm building that's a CMS. The idea is that a webpage can have many rows, and in each row, have many articles. The articles have an image, header, and paragraph.

To save the images, I'm using the edgee:slingshot package. Since sendFile($img, articlesArray); is asynchronous, the code to Meteor.call() runs before the sendFile($img, articlesArray); finishes. This causes the webPage to be saved in mongodb without any row arrays saved.

I am able to create the webPage without the rows (and their articles) in the database even though the console.log shows the webpage being set up correctly (with rows and articles - see bottom screenshot). It's as if the webPageAttributes are not being passed to the Meteor.call() . I am at a loss if this is a scoping problem or an async problem.

Here's the generated HTML: 在此处输入图片说明

Here is the client-side code that runs on form submit:

'submit form': function(e, template) {
    e.preventDefault();

    var articlesArray = [];
    var rowDivsArray = template.findAll('.row');

    _.map(rowDivsArray, function(div) {
        if (div.getAttribute("id") != null) {
            articlesArray.push(div);
        }
    });

    var articles = _.map(articlesArray, function(rowDiv) {
        var articlesArray = [];

         _.each(rowDiv, function() {
            var $rowDiv = $(rowDiv);
            var $articles = $rowDiv.find('div.article-container');

            $.each($articles, function(index, value) {
                var $img = $(value).find('input:file')[0].files[0];
                // console.log("$img: ", $img);
                var $input = $(value).find('input.form-control');
                var $text = $(value).find('textarea');
                var $style = $(value).find('input:radio:checked');

                var createArticle = function(downloadUrl){
                    console.log("createArticle() init");
                    var $article = {
                        img: downloadUrl,
                        header: $input.val(),
                        paragraph: $text.val(),
                        style: $style.val()
                    };
                    return $article;
                };

                var sendFile = function(img, articlesArray){
                    var articlesArray = articlesArray;
                    var upload = new Slingshot.Upload("articleImgUpload");
                    upload.send(img, function (error, downloadUrl) {
                        if (error) {
                            console.log("ERROR in upload: ", error);
                        } else {
                            createTheArticleAndAddToArray(downloadUrl);
                            return;
                        }
                    });
                    return upload;
                };

                // this is async, I need this to execute,
                sendFile($img, articlesArray);

                var createTheArticleAndAddToArray = function(downloadUrl) {
                    console.log("createTheArticleAndAddToArray() articlesArray before push: ", articlesArray);
                    var $article = createArticle(downloadUrl);
                    articlesArray.push($article);

                    console.log("createTheArticleAndAddToArray() articlesArray after push: ", articlesArray);
                    return articlesArray;
                };
            });
        });
        return articlesArray;
    });

    var makeWebPage = function(articles) {
        var webPage = {
            title: $(e.target).find('[name=title]').val(),
            rows: articles
        };
        return webPage;
    };

    var webPage = makeWebPage(articles);

    Meteor.call('newWebPageInsert', webPage, function(error, result) {
        if (error) {
            return alert(error.reason);
        } else {
            Router.go('/new-web-page');
            console.log("result from Meteor.call: ", result);
        }
    });
}

Here is the server-side code to insert the webPage into the db:

    // this will create the webPage with all its attributes, but no rows array and no articles
        Meteor.methods({
            newWebPageInsert: function(webPageAttributes) {  
                console.log("webPageAttributes argument in newWebPageInsert: ", webPageAttributes);         
                check(Meteor.userId(), String);
                check(webPageAttributes, {
                    title: String,
                    rows: Array
                });
                var user = Meteor.user();
                var webPage = _.extend(webPageAttributes, {
                    userId: user._id,
                    submitted: new Date()
                });
                var webPageId = PublicWebPages.insert(webPage);
                return {
                    _id: webPageId
                };

            }

The goal is one webPage, with a rows array, and the rows array has sub-arrays representing rows of the page. The sub-array contains article objects.

webPage: {
  title: "Page Title",
  rows: [
     [{img: "path to AWS", header: "article header", paragraph: "article paragraphs"}, {img: "path to AWS", header: "article header", paragraph: "article paragraphs"}], // 1st row 
     [{img: "path to AWS", header: "article header", paragraph: "article paragraphs"}, {...}]  // 2nd row
  ]
}

If I move the Meteor.call() to the else clause of the upload.send() , then a webPage is saved for every article with all the article attributes. If I leave the code as shown, the webPage is created, but WITHOUT any rows or articles. So confusing, since the console.log shows the webPage is set up correctly with the articles and form.

Do I pause the Meteor.call() to wait? Again it appears that the webPage.rows are not being passed correctly to the newWebPageInsert . How do I pass the webPage.rows to newWebPageInsert() in the Meteor.methods() ? Is this the correct logic to follow or should there be a pause somewhere else?

UPDATE 1: Looks like the Meteor.call() is being run prior to the rows and articles being created which is why they are not being saved.

Proof in this console.log: 创建文章之前保存的webPage对象

So it seems to be an async issue.... how to I pause the Meteor.call() until the articles are created?

Here's the console.log of the objects and their timing: 在此处输入图片说明

UPDATE 2: Timing and scope is preserved with a change in the code:

'submit form': function(e, template) {
    e.preventDefault();

    var articlesArray = [];
    var rowDivsArray = template.findAll('.row');
    // console.log("rowDivsArray: ", rowDivsArray);

    _.map(rowDivsArray, function(div) {
        if (div.getAttribute("id") != null) {
            articlesArray.push(div);
        }
    });

    // console.log("articlesArray: ", articlesArray);

    var articles = _.map(articlesArray, function(rowDiv) {
        // console.log("rowDiv: ", rowDiv); // get row's articles
        var articlesArray = []; // {img: "https://childrens-center.s3.amazonaws.com/undefined/full-style.png", header: "hell yeah", paragraph:"test 1"}

         _.each(rowDiv, function() {
            var $rowDiv = $(rowDiv);
            var $articles = $rowDiv.find('div.article-container');

            $.each($articles, function(index, value) {
                var $img = $(value).find('input:file')[0].files[0];
                // console.log("$img: ", $img);
                var $input = $(value).find('input.form-control');
                var $text = $(value).find('textarea');
                var $style = $(value).find('input:radio:checked');

                var createArticle = function(downloadUrl){
                    console.log("createArticle() init");
                    var $article = {
                        img: downloadUrl,
                        header: $input.val(),
                        paragraph: $text.val(),
                        style: $style.val()
                    };
                    return $article;
                };

                var sendFile = function(img, articlesArray){
                    console.log("articlesArray passed to sendFile line 250: ", articlesArray);
                    var articlesArray = articlesArray;
                    var upload = new Slingshot.Upload("articleImgUpload");
                    upload.send(img, function (error, downloadUrl) {
                        if (error) {
                            console.log("ERROR in upload: ", error);
                        } else {
                            // console.log("the articles array in sendfile before push line 257: ", articlesArray);

                            return createTheArticleAndAddToArray(downloadUrl);  
                        }
                    });
                    return upload;
                };

                // this is async, I need this to execute,
                sendFile($img, articlesArray);

                // will need to create the $article and push into db
                var createTheArticleAndAddToArray = function(downloadUrl) {
                    console.log("createTheArticleAndAddToArray() articlesArray before push: ", articlesArray);
                    var $article = createArticle(downloadUrl);
                    articlesArray.push($article);

                    console.log("createTheArticleAndAddToArray() articlesArray after push: ", articlesArray);
                    var articles = articlesArray;
                    var makeWebPage = function(articles) {
                        // console.log("articles within makeWebPage(): ", articles);
                        var webPage = {
                            title: $(e.target).find('[name=title]').val(),
                            rows: articles
                        };

                        Meteor.call('newWebPageInsert', webPage, function(error, result) {
                            console.log("webPage in the Meteor.call line 300: ", webPage);
                            console.log("webPage.rows in the Meteor.call line 300: ", webPage.rows);
                            if (error) {
                                return alert(error.reason);
                            } else {
                                Router.go('/new-web-page');
                                console.log("result from Meteor.call: ", result);
                            }
                        });
                        return webPage;
                    };

                    console.log("articles to insert line 294: ", articles);
                    var webPage = makeWebPage(articles);
                    return articlesArray;
                };
            });
        });

        return articlesArray;
    });
}

Now just need to control the looping to stop once all articles have been collected, and then call the makeNewWebPage() once.

Try this.

 Meteor.subscribe('whateverDataYouAreWaiting', Session.get('whateverDataYouAreWaiting'), {
              onError: function(){
                console.log('Error');
              },
              onReady: function() {
              //Here trigger a Session to flag the documents are ready, or call        Meteor.call
              }
            });

I ended up refactoring so that the articles would not be embedded in the web page, and so a separate post would be made to Amazon Storage per article. It still needs more work eg error handling but the core functionality is there.

Template.addWebPageArticles.events({
    'submit form': function(e, template) {
        e.preventDefault();     
        var rowsArray = template.findAll('.row');

        var allRowsWithNumberAssignedArray = [];
        _.each(rowsArray, function(row) {
            Session.set('rowNumber', (Session.get('rowNumber') + 1));
            $(row).attr( "id", "row" + Session.get('rowNumber') );
            allRowsWithNumberAssignedArray.push(row);
        });

        var rowsWithNumberAssignedThatContainArticlesArray = _.rest(allRowsWithNumberAssignedArray, 1);

        _.each(rowsWithNumberAssignedThatContainArticlesArray, function(row) {
            var articlesInRowInstanceArray = $(row).find('.article-container');

            _.each(articlesInRowInstanceArray, function(articleContainerDiv) {
                var article = {
                    page_id: template.data._id,
                    row_number: $(articleContainerDiv).parent().attr('id'),
                    place_in_row: String( _.indexOf(articlesInRowInstanceArray, articleContainerDiv) ),
                    image_path: $(articleContainerDiv).find('input:file')[0].files[0],
                    header: $(articleContainerDiv).find('input.form-control').val(),
                    paragraph: $(articleContainerDiv).find('textarea').val(),
                    style: $(articleContainerDiv).find('input:radio:checked').val()
                }
                var imgToUpload = function(article) {
                    var image = article.image_path;
                    return image;
                }

                var image = imgToUpload(article);

                var sendFile = function(image){
                    var upload = new Slingshot.Upload("articleImgUpload");
                    upload.send(image, function (error, downloadUrl) {
                        if (error) {
                            console.log("ERROR in upload: ", error);
                        } else {

                            var updateArticle = function(downloadUrl) {
                                article.image_path = downloadUrl;
                                return article;
                            }
                            var articleReturned = updateArticle(downloadUrl);

                            Meteor.call('newWebPageArticleInsert', articleReturned, function(error, result) {
                                if (error) {
                                    return alert(error.reason);
                                } else {
                                    Router.go('/new-web-page');
                                }
                            });
                        }
                    });
                    return upload;
                };

                sendFile(image);
            });
        });
    }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM