Chanpory
Chanpory

Reputation: 3095

Meteor JS: How do I insert a document into a collection, but only on the client-side?

I'm new to Meteor and building a simple app to learn the framework. The app I'm building let's you place words on an image of a kitten.

The desired behavior is this:

A user clicks anywhere on the kitten, and a contenteditable element appears letting the user enter text. Clicking outside of the element saves the element, and it remains in place.

The problem I'm running into:

If I have two browser windows opened with the app, and I click on one kitten in one window, an empty field appears in both windows. Ideally, the empty field would appear only on the window I clicked on. Once a word is saved, then in should be visible in both windows.

My question:

Is there a way to insert a document into a collection on the client-side only, and then use upsert later to add the document to servert-side collection?

Here's what I tried:

I created a stub method that exists only on the client-side for inserting the document. The problem with this, is that when I click on the image, an empty field appears for a split-second, and then disappears again.

Here's the code:

image-tags.js

if (Meteor.isClient) {
  var isEditing;

  Template.image.image_source = function () {
    return "http://placekitten.com/g/800/600";
  };

  Template.tag.rendered = function(){
    var tag = this.find('.tag');
    if (isEditing && !tag.innerText) {
      tag.focus();
    }
  }

  Template.image.events({
    'click img' : function (e) {
      if (isEditing) {
        isEditing = false;
      } else {
        isEditing = true;
        var mouseX = e.offsetX;
        var mouseY = e.offsetY;

        // Tags.insert({x:mouseX, y:mouseY});

        // Insert tag on the client-side only.
        // Upsert later when the field is not empty.
        Meteor.call('insertTag', {x:mouseX, y:mouseY});
      }
    },

    'click .tag' : function (e) {
      isEditing = true;
    },

    'blur .tag' : function (e) {
      var currentTagId = this._id;
      var text = e.target.innerText;

      if(text) {
        Tags.upsert(currentTagId, {$set: {name: text}});
      } else {
        Tags.remove(currentTagId);
      }
    }
  });

  Template.image.helpers({
    tags: function() {
      return Tags.find();
    }
  });

  // Define methods for the collections
  Meteor.methods({
    insertTag: function(attr) {
      Tags.insert({x:attr.x, y:attr.y});
    }
  });
}

// Collections
Tags = new Meteor.Collection('tags');

image-tags.html

<head>
  <title>Image Tagger</title>
</head>

<body>
  {{> image}}
</body>

<template name="image">
  <figure>
    <img src="{{image_source}}" />
    <figcaption class="tags">
        {{#each tags}}
          {{> tag}}
        {{/each}}
      </figcaption>
  </figure>
</template>


<template name="tag">
  <div class="tag" contenteditable style="left: {{x}}px; top: {{y}}px;">
    {{name}}
  </div>
</template>

Upvotes: 4

Views: 1611

Answers (2)

Rebolon
Rebolon

Reputation: 1307

If you create the collection on client-side only you might have problem if you are disconnected : your new docs won't be stored on server.

To my mind the best way is to set a property "published" or "editing" or "status" (with value published / eiditing / ...) in your document. Then your publish methods should return :

  1. All the documents of the current user
  2. All the documents "published" or not in "editing" status

When a user create a document it's stored on server but with the editing status. then when you save you can decide to publish it, and then all other user will receive the document in their subscription.

Hope that alternative solution will help you

Upvotes: 0

sbking
sbking

Reputation: 7680

You should store the temporary tag (and probably your isEditing var) in Session:

Session.set("isEditing", true);
Session.set("newTag", {x:mouseX, y:mouseY});

You can also create a local collection by passing null instead of a collection name when initializing it. However, Session should work for what you're doing. Check out the leaderboard for an example.

Edit:

<figcaption class="tags">
  {{#each tags}}
    {{> tag}}
  {{/each}}
  {{#with newTag}}
    {{> tag}}
  {{/with}}
</figcaption>

Template.image.newTag = function() {
  return Session.get("newTag");
}

Upvotes: 4

Related Questions