How do I use X-editable on dynamic fields in a Meteor template now with Blaze?

前端 未结 6 2067
忘了有多久
忘了有多久 2021-01-01 05:42

I had x-editable working in Meteor 0.7.2 but since upgrading to 0.8.0 it no longer renders correctly. I tend to end up with a bunch of Empty tags. This is frustrating becaus

相关标签:
6条回答
  • 2021-01-01 05:53

    Working off of Andrew's answer, I was able to get this to work for me. It's not in coffeescript, also I think the Blaze.getCurrentData() might now be Blaze.getData() as per the Meteor docs.

    Template:

    <template name="objective">
        <p id="objective" class="editable" data-type="textarea" data-placeholder="Enter text" data-emptytext="Click to enter text" data-rows="4">{{objective.value}}</p>
    </template>
    

    Code:

    Template.objective.rendered = function(){
        var self = this;
        this.autorun(function(){
            data = Blaze.getData();
            self.$("#objective.editable").editable("destroy").editable({
                placement: "bottom",
                display: function(){},
                success: function(response, newValue){
                    var insert = {
                        "contract_id":data._id,
                        "value": newValue
                    };
                    Meteor.call('update_objective', insert);
                }
            });
        });
    };
    

    There are probably improvements I can make and I'm happy to hear them, but I spent a lot of time dealing with bad coffeescript translation (kept telling me to use return all the time), so I wanted to add another example.

    0 讨论(0)
  • 2021-01-01 05:54

    Updated for Meteor 0.8.3+

    This covered all cases for me (see below the code). This uses pretty fine-grained reactivity and will update the x-editable instance only when the specified value changes.

    Template:

    <!-- once off for your entire project -->
    <template name="xedit">
        {{> UI.contentBlock}}
    </template>
    
    <!-- per instance -->
    <template name="userInfo">
      {{#xedit value=profile.name}}<a>{{profile.name}}</a>{{/xedit}}
    </template>
    

    Client Javascript (for Meteor 0.8.3+):

    // once off for your entire project
    Template.xedit.rendered = function() {
      var container = this.$('*').eq(0);
      this.autorun(function() {
        var value = Blaze.getCurrentData().value;
        var elData = container.data();
        if (elData && elData.editable) {
          elData.editable.setValue(value, true);
          // no idea why this is necessary; xeditable bug?
          if (elData.editableContainer)
            elData.editableContainer.formOptions.value = elData.editable.value;
        }
      });
    }
    
    // per instance; change selector as necessary
    Template.userInfo.rendered = function() {
      // Note you don't need all the :not(.editable) stuff in Blaze
      this.$('a').editable({
        success: function(response, newValue) {
          // according to your needs
          var docId = $(this).closest('[data-user-id]').attr('data-user-id');
          var query = { $set: {} }; query['$set']['profile.username'] = newValue;
          Meteor.users.update(docId, query);
        }
      });
    });
    

    You can see it in action at http://doingthiswithmeteor.com/ (with two windows open). You need to be logged in, but try change any of your info on the "me" page.

    1. Setup x-editable in rendered() as usual
    2. Ability for custom display functions, "empty" values, etc.
    3. Open in two windows. Change value in win1, click on value in win2.. the POPUP should display the correct value.
    4. Support for custom types like dates and arrays from custom helpers

    Just implemented this... still doing some testing but feedback welcome. This replaces my previous helper workaround.

    0 讨论(0)
  • 2021-01-01 06:04

    EDIT: Much easier to implement now in Meteor 0.8.3:

    Template:

    <template name="docTitle">
        <span class="editable" title="Rename this document" data-autotext="never">{{this}}</span>
    </template>
    

    Code:

    Template.docTitle.rendered = ->
      tmplInst = this
    
      this.autorun ->
        # Trigger this whenever data source changes
        Blaze.getCurrentData()
    
        # Destroy old editable if it exists
        tmplInst.$(".editable").editable("destroy").editable
          display: ->
          success: (response, newValue) -> # do stuff
    

    For this to be most efficient, make sure the data context of the editable template is only the field being edited, as in the example above with {{> docTitle someHelper}}.


    Outdated information follows for Meteor 0.8.0 to 0.8.2

    I also had to do this but wasn't sure about using the global helper in my app. So I tried to accomplish it by just changing the behavior of the editable.

    The main things that needed to be done, after perusing the docs and source, were:

    • Set the value of the form from the field text
    • Override the display function so that reactivity of text updated by Meteor doesn't break
    • Make sure the above two functions don't break

    Here's the code (apologies for Coffeescript):

    Template.foo.rendered = ->
      container = @$('div.editable')
      settings =
        # When opening the popover, get the value from text
        value: -> $.trim container.text()
        # Don't set innerText ourselves, let Meteor update to preserve reactivity
        display: ->
        success: (response, newValue) =>
          FooCollection.update @data._id,
            $set: { field: newValue }
          # Reconstruct the editable so it shows the correct form value next time
          container.editable('destroy').editable(settings)
      container.editable(settings)
    

    This is ugly because it destroys and re-creates the popover after setting a new value, so that the form field updates from the correct value.

    After some more reverse engineering, I found a cleaner way to do this that doesn't involve destroying the editable. Gadi was right on that container.data().editableContainer.formOptions.value has something to do with it. It's because this value is set after the update because x-editable thinks it can cache this now. Well, it can't, so we replace this with the original function so the value continues being updated from the text field.

    Template.tsAdminBatchEditDesc.rendered = ->
      container = @$('div.editable')
      grabValue = -> $.trim container.text() # Always get reactively updated value
      container.editable
        value: grabValue
        display: -> # Never set text; have Meteor update to preserve reactivity
        success: (response, newValue) =>
          Batches.update @data._id,
            $set: { desc: newValue }
          # Thinks it knows the value, but it actually doesn't - grab a fresh value each time
          Meteor.defer -> container.data('editableContainer').formOptions.value = grabValue
    

    Notes:

    • $.trim above was taken from the default behavior to render value
    • For more discussion about this see https://github.com/nate-strauser/meteor-x-editable-bootstrap/issues/15
    • This is all an ugly hack, hopefully in the future we'll see better support for two-way variable bindings in Meteor.

    I will try to make this more concise in the future pending better support from Meteor for depending on data reactively.

    0 讨论(0)
  • 2021-01-01 06:05

    If Andrew's answer worked for you, and you have a lot of fields like this, you may find convenient to use a function to create the required templates. Here is an example

    <template name="main">
      <div style="height:200px"></div>
      <div class="container">
        <div class="jumbotron">
          {{#each editables}}
            {{> editable1}}
            {{> editable2}}
          {{/each}}
        </div>
      </div>
    </template>
    
    <template name="editable1">
      <p id="{{id}}" data-type="textarea" data-placeholder="Enter text" data-emptytext="Click to enter text" data-rows="4">{{content}}</p>
    </template>
    <template name="editable2">
      <p id="{{id}}" data-type="textarea" data-placeholder="Enter text" data-emptytext="Click to enter text" data-rows="4">{{content}}</p>
    </template>
    

    and in the js:

    Template.main.editables = function(){
      return Objects.find({});
    };
    
    function xeditFactory(collection, template, field){
      template.content = function(){ return this[field]; };
      template.id      = function(){ return 'xedit_'+this._id+'_'+field; };
      template.rendered = function(){
        var container = this.$('#xedit_'+this.data._id+'_'+field);
        console.log(container);
        var grabValue = function() {
          return $.trim(container.text());
        };
        return container.editable({
          value: grabValue,
          display: function() {},
          success: (function(_this) {
            return function(response, newValue) {
              var set = {};
              set[field]=newValue;
              collection.update(_this.data._id, {$set:set});
              return Meteor.defer(function() {
                return container.data('editableContainer').formOptions.value = grabValue;
              });
            };
          })(this)
        });
      };
    }
    
    xeditFactory(Objects, Template.editable1, 'field1');
    xeditFactory(Objects, Template.editable2, 'field2');
    
    0 讨论(0)
  • 2021-01-01 06:06

    This is my simplified approach, based on gadicc's post (tested with Meteor 0.9.3).

    Let's say there is a MyDocuments collection, which is rendered via documentList template. Each document in the collection has the title field, which we want to edit using xedtiable.

    document.html

    <template name="documentList">
        {{#each documents}}
            {{>document}}
        {{/each}}
    </template>
    
    <template name="document">
       <p>Title: {{>xeditable titleOptions}}</p>
    </document>
    

    document.js

    Template.document.titleOptions = function () {
        return {
            // We need to specify collection, id and field to autoupdate MongoDb
            collection: MyDocuments,
            id: this._id,
            field: 'title',
            value: this.title             
        }
    }
    

    xeditable.html

    <template name="xeditable">
        <span class="xeditable">{{value}}</span>
    </template>
    

    xeditable.js

    Template.xeditable.rendered = function () {
        var container = this.$('*').eq(0);
        if (!container.hasClass('processed')) {
            container.addClass('processed');
            var options = _.extend(this.data, {
                // Default success function, saves document do database
                success: function (response, value) {
                    var options = $(this).data().editable.options;
                    if (options.collection && options.id && options.field) {
                        var update = {};
                        update[options.field] = value;
                        options.collection.update(options.id, {
                            $set: update
                        });
                    }
                }
            });
            container.editable(options);
        }
    
        this.autorun(function () {
            var value = Blaze.getData().value;
            var elData = container.data();
            if (elData && elData.editable) {
                elData.editable.setValue(value, true);
                // no idea why this is necessary; xeditable bug?
                if (elData.editableContainer)
                    elData.editableContainer.formOptions.value = elData.editable.value;
            }
        });
    }
    
    0 讨论(0)
  • 2021-01-01 06:09

    Yet another implementation working with iron-router and managing Collection2 validation:

    The control

    div(id="text" class="editable" data-type="text" data-pk="#{_id}" data-name="address" data-value="#{address}" data-context="Buildings") #{address}
    

    And the JS code:

      setTimeout( ->   #needed to work with iron-router
        $(".editable").editable
          placement: "auto top"
          display: ->
          success: (response, newValue) ->
            newVal = {}
            oldVal = $.trim $(this).data("value")
            name = $(this).data("name")
            newVal[name] = newValue
            eval($(this).data("context")).update $(this).data("pk"), $set: newVal
            , (error) ->
              Notifications.error error.message
              Meteor.defer -> $(".editable[data-name=" + name + "]").data('editableContainer').formOptions.value = oldVal
    
            console.log "set new value to " + newValue
            Session.set "text", newValue
      ,500)
    

    I couldn't find a way to set the data context automatically. I'm sure it shouldn't be very difficult. Any help welcome!

    0 讨论(0)
提交回复
热议问题