Meteor JS: How can I effectively manage the state of a unique template?

I'm currently delving into the realms of Session and reactive data sources within the Meteor JS framework. They prove to be quite useful for managing global UI states. However, I've encountered a challenge in scoping them to a specific instance of a template.

My Objective

I have multiple contenteditable elements on a single webpage, each accompanied by an "Edit" button. On clicking the Edit button, the respective element should be focused on with "Save" and "Cancel" buttons displayed alongside.

Upon clicking "Cancel", any changes should be discarded, and the template instance should refresh with the original content.

The Code Implementation So Far

// Helper
Template.form.helpers({
  editState: function() {
    return Session.get("editState");
  }
});

// Rendered
Template.form.rendered = function(e){
  var $this = $(this.firstNode);
  var formField = this.find('.form-field');
  if (Session.get("editState")) formField.focus();
};

// Event map
Template.form.events({
  'click .edit-btn' : function (e, template) {
    e.preventDefault();
    Session.set("editState", "is-editing");
  },

  'click .cancel-btn' : function (e, template) {
    e.preventDefault();
    Session.set("editState", null);
  },
});

// Template
<template name="form">
  <div class="{{editState}}">
    <p class="form-field" contenteditable>
      {{descriptionText}}
    </p>
  </div>
  <a href="#" class="edit-btn">Edit</a>
  <a href="#" class="save-btn">Save</a>
  <a href="#" class="cancel-btn">Cancel</a>
</template>


// CSS
.edit-btn
.cancel-btn,
.save-btn {
  display: inline-block;
}

.cancel-btn,
.save-btn {
  display: none;
}

.is-editing .cancel-btn,
.is-editing .save-btn  {
  display: inline-block;
}

The Dilemma

When dealing with multiple instances of the Form template, the .form-field gets focused on every instance, rather than just the one being edited. How can I ensure that only the currently edited element is focused upon?

Answer №1

To display a template with data, you simply pass an object to it when adding it to a page.

The data can be as simple as the key to use in the Session for editState.

For example, display the template with

Template.form({editStateKey:'editState-topForm'})

You can create a handlebars helper like this:

Handlebars.registerHelper('formWithOptions', 
  function(editStateKey){
    return Template.form({editStateKey:editStateKey})
});

Then include it in your template like so:

{{{formWithOptions 'editState-topForm'}}}
(remember the triple {, })

Next, update references from Session.x('editState') to Session.x(this.editStateKey) or

Session.x(this.data.editStateKey)

Template.form.helpers({
  editState: function() {
    return Session.get(this.editStateKey);
  }
});

// Rendered
Template.form.rendered = function(e){
  var $this = $(this.firstNode);
  var formField = this.find('.form-field');
  if (Session.get(this.data.editStateKey)) formField.focus();
};

// Event map
Template.form.events({
  'click .edit-btn' : function (e, template) {
    e.preventDefault();
    Session.set(this.editStateKey, "is-editing");
  },

  'click .cancel-btn' : function (e, template) {
    e.preventDefault();
    Session.set(this.editStateKey, null);
  },
});

Keep in mind: if you're using iron-router, it provides additional API's for passing data to templates.

Also, in meteor 1.0, there should be improved support for creating custom widgets, which will offer better control in situations like this.

Answer №2

It is my standard practice to steer clear of Session usage in most scenarios. The broad scope of Session tends to promote poor habits and a lack of proper organization when working on larger applications. Additionally, the global nature of Session can cause issues when dealing with multiple instances of a template. Due to these drawbacks, I believe there are more scalable alternatives available.

Potential Alternatives

1 Using addClass/removeClass

Rather than setting a state and reacting to it elsewhere, actions can be performed directly. Utilizing classes to display or hide elements as needed:

'click .js-edit-action': function(event, t) {
    var $this = $(event.currentTarget),
    container = $this.parents('.phenom-comment');

    // open and focus
    container.addClass('editing');
    container.find('textarea').focus();
},

'click .js-confirm-delete-action': function(event, t) {
      CardComments.remove(this._id);
},

2 Scoped ReactiveVar within the template instance

if (Meteor.isClient) {  
  Template.hello.created = function () {
  // counter starts at 0
  this.counter = new ReactiveVar(0);
};

Template.hello.helpers({
  counter: function () {
  return Template.instance().counter.get();
  }
});

Template.hello.events({
  'click button': function (event, template) {
    // increment the counter when button is clicked
    template.counter.set(template.counter.get() + 1);
  }
});
}

3 Utilizing Iron-Router's state variables

Obtain

Router.route('/posts/:_id', {name: 'post'});

PostController = RouteController.extend({
  action: function () {
    // set the reactive state variable "postId" with a value
    // of the id from our url
    this.state.set('postId', this.params._id);
    this.render();
  }
});

Define

Template.Post.helpers({
  postId: function () {
    var controller = Iron.controller();

    // reactively return the value of postId
    return controller.state.get('postId');
  }
});

https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#setting-reactive-state-variables

4 Modification of Collection data

An alternative method involves updating data in your collection directly. This approach can be suitable depending on the circumstances.

5 Adjusting the data context

In my opinion, Session is often not the best choice. Additionally, I prefer not to use the #3 method as I believe having less dependency on iron-router could be beneficial in case a switch to another router package like "Flow" is considered.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using PHP Escape Functions in JavaScript

Can anyone help me with displaying item descriptions retrieved from a database query? Here is the code snippet that should display the description along with other details... <?php $type = "item"; $limit = 16; $preparedStatement = $SQL->prepare(& ...

What are some ways to monitor the movement of elements and activate functions at precise locations?

I am working on a project that involves a #ball element which, when clicked, utilizes jQuery animate to move downwards by 210px. The code I currently have is as follows: $('#ball').click(function() { $(this).animate({ top: '+=2 ...

Using JavaScript and jQuery to calculate the time difference between two values

Here is a code snippet for calculating the difference between two numbers: function calculateDifference(num1, num2) { if (num1 > num2) { return num1 - num2; } else { return num2 - num1; } } On document.ready event: var result = calculateD ...

Angular 2's Implementation of Hierarchical Dependency Injection

I'm encountering a problem with my Angular 2 application. I have a root component, AppComponent, where I've injected a service called ProductService. Now, I am trying to resolve this service in one of the child components, ProductList, but I keep ...

Send data from input to controller without using $scope

I am encountering an issue with the code below. Typically, I would resolve this problem using $scope, but this time I have been asked to find a solution without utilizing $scope in the controller. I am implementing the "controller as" syntax for managing ...

Updating HTML in Vue.js with data at a specific index value can be done by targeting

Experimenting with a combination of vue.js and vanilla to create a blackjack game. The card data is stored in the vue data() like this: data(){ return { cards: [ {id: 1, cardName: 'Ace of Spades', cardPic: ' ...

Validating dynamic textboxes in Angular with custom rules

Creating dynamic textboxes with *ngFor in Angular <tr *ngFor="let computer in _Computers; let i = index;"> <td>{{computer.Name}}</td><td>{{computer.Optional}}</td> <td> <input matInput [formControl] = " ...

Find the element that is being scrolled in order to delete its attributes

Issue with the sidebar causing whitespace on mobile devices, and scroll properties need to be removed. When expanding the sidebar, white space appears below it. Various display modes have been tried, but they all push elements below instead of keeping th ...

Instructions for user to upload and play an MP4 file on their PC

For my web project that utilizes node.js, HTML, and JavaScript, I am looking to incorporate a video player element. I want users to have the ability to click a button and play an MP4 file directly on the webpage. How can I achieve this? I currently have s ...

Creating a duplicate of a Flex object in HTML without the need to reinitialize

I'm currently developing a flash object that involves processing a large number of images. My goal is to load multiple flash objects on the same page in order to capture an image, make adjustments, and then display it within each flash object. Howeve ...

Utilizing numerous Nuxt vuetify textfield components as properties

Trying to create a dynamic form component that can utilize different v-models for requesting data. Component: <v-form> <v-container> <v-row> <v-col cols="12" md="4"> <v ...

The imported package is not functioning properly within the project

I've recently developed a Typescript Package and I want to test it in an application before publishing it on NPM. The main file (index.ts) of the package is structured like this => import Builder from './core/builder'; export default ...

Why is my "npm install" pulling in unnecessary libraries that I didn't even mention?

I've listed my dependencies in package.json as follows: { "cookie-parser": "~1.0.1", "body-parser": "~1.0.0", "express": "~3.5.0", "socket.io":"1.0", "mongodb":"2.2.21", "request":"2.79.0", "q":"1.4.1", "bcryptjs":"2.4.0", "jsonw ...

Align list items in the center horizontally regardless of the width

I have created this container element: export const Container = styled.section<ContainerProps>` display: flex; justify-content: center; `; The current setup is vertically centering three list items within the container. However, I am now looking ...

Having trouble with React Testing Library throwing an error every time I try to use fireEvent on an input text?

When attempting to utilize the "fireEvent" method from React Testing Library, I encountered an error as shown below: https://i.sstatic.net/ExH4u.png MainSection.test.js: test('Verifying SearchBar functionality', async () => { render(< ...

Is there a more effective method for detecting changes in a class variable in JavaScript, aside from using setInterval()?

Is there a more readable way to monitor changes of a class variable from its instance? Although I can use setInterval() to achieve this, the code becomes quite difficult to read. let calibrator = new Calibrator("hardwareName"); calibrator.connect(); let ...

Instructions for creating a slush machine

Today, I tried to create a slush generator but ran into some issues. Even after following tutorials online and double-checking my work, the generator doesn't seem to be functioning properly. If anyone can identify what I might have done wrong, please ...

Utilizing Nuxt3's auto-import feature alongside Eslint

I'm having trouble finding an eslint setup that is compatible with Nuxt3's auto-import feature to prevent no-undef errors. I have tried various packages like @antfu/eslint-config, plugin:nuxt/recommended, @nuxt/eslint-config, @nuxtjs/eslint-confi ...

I'm having trouble executing the straightforward code I discovered on GitHub

https://github.com/Valish/sherdog-api Upon downloading the sherdog-api, I embarked on installing node.js as a prerequisite for using this new tool, despite having no prior experience with such software. Following that, I opened up the command prompt and n ...

AngularJS variable assignment with HTTP GET operation

The angular filter I have set up is functioning perfectly: categorieFilter = angular.module("categorieFilter", []) categorieFilter.controller("catFilter", ["$scope", "store", function($scope, store){ $scope.search = ""; $scope.products = []; $ ...