问题
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 because the data is there, just not by the time the rendered function is fired.
<template name="clientPage">
<header>{{> clientPageTitleUpdate}}</header>
</template>
<template name="clientPageTitleUpdate">
<h1><span class="title-update editable" data-type="text" data-pk="{{_id}}" data-name="title" data-value="{{title}}">{{title}}</span></h1>
</template>
Template.clientPageTitleUpdate.rendered = function() {
console.log(this.$(".title-update").text());
// set up inline as defaule for x-editable
$.fn.editable.defaults.mode = 'inline';
$(".title-update.editable:not(.editable-click)").editable('destroy').editable({
url: "empty",
toggle: "dblclick",
success: function (response, newValue) {
// update value in db
var currentClientId = $(this).data("pk");
var clientProperties = { title: newValue };
Clients.update(currentClientId, {$set: clientProperties}, function(error) {
if (error) {
Errors.throw(error.message)
}
});
}// success
});
}
I have tried the "new" rendered method of embeding the this into another template as explained here and it doesn't seem to work either.
What is the best way to use x-editable now that rendered only fires once and doesn't make sure the data is there.
I am using Iron Router and my templates are not embeded in an {{#each}} block which seems to be the basic solution to the new rendered model.
This question is related to this older topic about x-editable in a meteor template.
Any help whatsoever would be super appreciated here. I am at a loss. Thanks
回答1:
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.
回答2:
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.
- Setup x-editable in rendered() as usual
- Ability for custom display functions, "empty" values, etc.
- Open in two windows. Change value in win1, click on value in win2.. the POPUP should display the correct value.
- 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.
回答3:
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');
回答4:
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!
回答5:
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.
回答6:
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;
}
});
}
来源:https://stackoverflow.com/questions/22867690/how-do-i-use-x-editable-on-dynamic-fields-in-a-meteor-template-now-with-blaze