I use bootstrap3-wysiwyg based on wysihtml5
I seek solution for
On "Enter" key insert "br" not "p"
Button for change font size with input value
Use 'br'
$("#TextContent").wysihtml5({useLineBreaks: true, ...})
Font-size
var myCustomTemplates = {
custom1: function (context) {
var s = ""
s += '<li class="dropdown">';
s += '<a class="btn btn-default dropdown-toggle" data-toggle="dropdown" tabindex="-1" aria-expanded="false">';
s += '<span class="current-color">Size</span>';
s += '<b class="caret"></b>';
s += '</a>';
s += '<ul class="dropdown-menu" style="margin-top: 0px;">';
s += '<li><a class="fontSize" data-wysihtml5-command="fontSize" data-wysihtml5-command-value="fs5">5px</a></li>';
s += '</ul>';
s += '</li>';
return s;
}
};
$("#TextContent").wysihtml5(
{
customTemplates: myCustomTemplates,
"stylesheets": ["~/Content/wysiwyg/editor.css"]
});
editor.css
.wysiwyg-font-size-fs5 {
font-size: 5px;
}
Related
There are 5 buttons triggering a modal.
In modal, a string is inputted and this is then set as the text for the respective button i.e. the button that triggered it.
I used event.relatedTarget and was able to access the respective button.
This worked for the first time but in second attempt (clicking another button to trigger the modal) the event.relatedTarget took in consideration both the buttons. And third time it took all three and so on.
Is there anyway to reset this mapping anyhow?
Here is my Modal made dynamically in JavaScript:
'<div class="modal fade" id="editTitleModal" role="dialog">'+
'<div class="modal-dialog modal-lg">'+
'<div class="modal-content">'+
'<div class="modal-header">'+
'<h4 class="modal-title">Edit column label</h4>'+
'<button type="button" class="close" data-dismiss="modal">×</button>'+
'</div>'+
'<div class="modal-body" style="padding: 2rem;">'+
'<h6 class="mb-2">Select a field for this column</h6>'+
"<select id='columnLabel' style='border: solid #000 0.1rem;' class='csv-select'>" +
"<option class='select-none' value='select-none'>Select Value</option>";
for (let i = 0; i < data.header.length; i += 1) {
html = html + "<option value='" + data.header[i][0].toString().substr(0, 30) + "'>"+ data.header[i][0].toString().substr(0, 30) +"</option>";
}
html = html + "</select>"+
'</div>'+
'<div class="modal-footer">'+
'<button type="button" style="width: 6rem; margin-left: 33rem;" class="blue-fill-btn" id="updateHeader">Confirm</button>'+
'<button type="button" style="width: 6rem; margin-right: 2rem;" class="outline-stand-btn" id="closeModal" data-dismiss="modal">Close</button>'+
'</div>'+
'</div>'+
'</div>'+
'</div>';
Here is one out of five buttons, calling the modal:
<td class='cellDesign btn' data-toggle='modal' onclick='sendToModal()' data-target='#editTitleModal'> data.header[i][0].toString().substr(0, 30) </td>
Here is the javascript function:
function sendToModal() {
$('#editTitleModal').one('show.bs.modal', function (event) {
$(document).on('click', '#updateHeader', function () {
console.log(event.relatedTarget);
if ($('#columnLabel')[0].value != 'select-none') {
$(event.relatedTarget).html($('#columnLabel')[0].value);
$('#closeModal').click();
}
});
});
}
There is a short time between the posting and the response from the server. How is it possible to cause your component to re-render when you get your positive response? I tried componentWillGetProps(){} and if-statements like
if(this.props.incomingItems){return: this.props.incomingItems}
but it none of them worked out. How did you solve this problem?
PS I'm using redux and axios for the requests.
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
import * as actions from '../../actions';
class eventView extends Component {
componentWillMount() {
this.props.eventView(this.props.params.eventID);
}
createNewRole(roleName){
this.props.createNewRole(roleName, this.props.params.eventID);
};
renderUsers(){
return this.props.eventDetails.userList.map((user)=>{
return(
<li className='list-group-item eventUserList' background-color="#f2f2f2" key={user._id}>
{user.userName}
</li>
);
});
};
deleteListItem(key){
const newKey = key.dispatchMarker.substr(44, 24);
this.props.RemoveRoleFromList(newKey)
this.props.fetchEvents();
}
renderRoles(){
return this.props.eventDetails.role.map((role)=>{
return(
<li className='list-group-item roleList' key={role._id}>
{role.roleName}
<img className="deleteListItem"
src="/img/trash.png"
key={role._id}
onClick={this.deleteListItem.bind(this)}/>
</li>
);
});
};
render() {
const { handleSubmit, fields: {eventName,location, eventPassword, roleName} } = this.props;
if(this.props.roleList){
console.log(this.props.roleList)
}
if (this.props.eventDetails){
return (
<div className='container-fluid'>
<div className="roleBox">
<form onSubmit={handleSubmit(this.createNewRole.bind(this))}>
<div>
<input {...roleName}
className="form-control roleBoxInputBar"
autoComplete="off"/>
<button className="RoleButton">Save</button>
</div>
<div className="listOfRoles">
<ul className="listOfRoles pre-scrollable">
{this.renderRoles()}
</ul>
</div>
</form>
</div>
<div>
<div>
<h1 className="eventName">
{this.props.eventDetails.eventName}
</h1>
</div>
<br/>
<table>
<tbody>
<tr>
<td className="eventViewTableLocation">Location:</td>
<td className="eventViewTable">{this.props.eventDetails.location}</td>
</tr>
<tr>
<td className="eventViewTableLocation">Date:</td>
<td className="eventViewTable">12/Feb/2018</td>
</tr>
<tr>
<td className="eventViewTableLocation">Time Left:</td>
<td className="eventViewTable">2 Days 2 Hours</td>
</tr>
</tbody>
</table>
</div>
<div className='eventViewUserBox'>
<h4 className="listOfUsers">Organisers:</h4>
<ul>
{this.renderUsers()}
</ul>
</div>
</div>
);
}else {
return (
<div>
</div>
);
}
}
}
function mapStateToProps(state) {
return { eventDetails: state.api.eventDetails };
return { roleList: state.api.roleList };
return { createdRole: state.api.createdRole };
}
export default reduxForm({
form: 'eventView',
fields: ['eventName', 'location', 'eventPassword', 'roleName']
}, mapStateToProps, actions)(eventView);
And my axios post goes something like this
export function createNewRole({roleName}, eventID){
return function(dispatch) {
axios.post(`${ROOT_URL}createRole/`+eventID, {roleName})
.then(response => {
if (response.data){
dispatch({
type: CREATED_ROLE,
payload: response.data,
});
};
})
.catch(response => dispatch(authError(response.data.error)));
};
};
Reducer:
export default function(state = {}, action) {
switch(action.type) {
case FETCH_ROLES:
return { ...state, roleList: action.payload };
case CREATED_ROLE:
return { ...state, createdRole: action.payload };
}
return state;
}
Thanks a lot!
function mapStateToProps(state) {
return { eventDetails: state.api.eventDetails };
return { roleList: state.api.roleList };
return { createdRole: state.api.createdRole };
}
This function always returns the first object. It should be:
function mapStateToProps(state) {
return {
eventDetails: state.api.eventDetails,
roleList: state.api.roleList,
createdRole: state.api.createdRole
};
}
I'm guessing roleList and createdRole are always undefined? Also it would be good if you would show the reducer.
Within the context of Meteor/React I have a table component that subscribes to a Mongo Database, this subscription has a limit parameter that can be updated via props. I'm not sure why but it appears that the componentDidMount Lifecycle function is firing almost continually leading to unpleasant flickering.
The code is quite lengthy but I've attached what I think is the problematic section - more generally, how do you go about isolating what change in state/props is leading to a re-render? I've tried monitoring Chrome Tools but see no change there.
Any feedback or help appreciated!
import React from 'react';
import booksSingleLine from '../booksTable/booksSingleLine';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class booksListingTable extends TrackerReact(React.Component) {
constructor(props) {
super(props);
console.log("Constructor props are" + this.props.LimitProp);
}
componentWillMount() {
console.log("Mounting booksListingTable");
console.log("Will mount props are" + this.props.LimitProp);
this.state = {
}
}
componentDidMount(props){
// console.log("Did mount and props are" +props.LimitProp);
var limit = this.props.LimitProp
limit = parseInt(limit) || 5;
this.state = {
subscription: {
booksData: Meteor.subscribe("allbooks",{sort: {_id:-1}, limit: limit})
}
}
}
componentWillReceiveProps(props) {
console.log("component will get new props " + props.LimitProp);
// Note that for this lifecycle function we have to reference the props not this.props
// console.log("component will get weird props " + this.props.LimitProp);
var limit = props.LimitProp
limit = parseInt(limit) || 5;
this.state = {
subscription: {
booksData: Meteor.subscribe("allbooks", {limit: limit})
}
}
}
componentWillUnmount() {
}
booksDataLoad(){
var filteredCity = this.props.FilterProp;
console.log("filter is " + filteredCity);
if (filteredCity) {
console.log("just passing a few things")
return (
remotebookss.find({location: filteredCity}).fetch()
)
}
else {
console.log("passing everything to table");
return(
remotebookss.find().fetch()
)}}
render() {
return(
<div>
<table className="ui celled table">
<thead>
<tr>
<th onClick ={this.props.HeaderOnClick}>Name</th>
<th>Date</th>
<th>Summary</th>
<th>Site address</th>
<th>Price is</th>
</tr></thead>
<tbody>
{this.booksDataLoad().map( (booksData)=> {
return <booksSingleLine key={booksData._id} booksData={booksData} />
})}
</tbody>
<tfoot>
<tr><th colspan="3">
<div class="ui right floated pagination menu">
<a class="icon item">
<i class="left chevron icon"></i>
</a>
<a class="item" onClick= {this.props.methodLoadMore}>Load More</a>
<a class="icon item">
<i class="right chevron icon"></i>
</a>
</div>
</th>
</tr></tfoot>
</table>
</div>
)
}
}
I would strongly recommend you read the following blog post --
https://www.discovermeteor.com/blog/data-loading-react/
Things I see:
You're resetting state all over the place
You're not managing the subscription as a clear object - your code leaks them (FYI).
Here's a version that uses props and default props to setup your Limit, with that it checks only on startup and change to change the subscription.
import React from 'react';
import booksSingleLine from '../booksTable/booksSingleLine';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class booksListingTable extends TrackerReact(React.Component) {
static propTypes = {
LimitProp: React.PropTypes.number,
}
static defaultProps = {
LimitProp: 5,
}
constructor() {
super();
console.log("Constructor props are" + this.props.LimitProp);
const subscription = Meteor.subscribe("allbooks",{sort: {_id:-1}, limit: this.props.LimitProp})
this.state = {
booksData: subscription,
}
}
componentWillReceiveProps(nextProps) {
console.log("component will get new props " + nextProps.LimitProp);
// Start new subscription - if it's changed
if (this.props.LimitProp != nextProps.limitProp) {
// Stop old subscription
this.state.booksData.stop()
// Setup new subscription
const subscription = Meteor.subscribe("allbooks",{sort: {_id:-1}, limit: nextProps.LimitProp})
this.setState({ booksData: subscription })
}
}
componentWillUnmount() {
// Stop old subscription
this.state.booksData.stop()
}
booksDataLoad(){
var filteredCity = this.props.FilterProp;
console.log("filter is " + filteredCity);
if (filteredCity) {
console.log("just passing a few things")
return (
remotebookss.find({location: filteredCity}).fetch()
)
}
else {
console.log("passing everything to table");
return(
remotebookss.find().fetch()
)
}
}
render() {
return(
<div>
<table className="ui celled table">
<thead>
<tr>
<th onClick ={this.props.HeaderOnClick}>Name</th>
<th>Date</th>
<th>Summary</th>
<th>Site address</th>
<th>Price is</th>
</tr></thead>
<tbody>
{this.booksDataLoad().map( (booksData)=> {
return <booksSingleLine key={booksData._id} booksData={booksData} />
})
}
</tbody>
<tfoot>
<tr><th colspan="3">
<div class="ui right floated pagination menu">
<a class="icon item">
<i class="left chevron icon"></i>
</a>
<a class="item" onClick= {this.props.methodLoadMore}>Load More</a>
<a class="icon item">
<i class="right chevron icon"></i>
</a>
</div>
</th>
</tr></tfoot>
</table>
</div>
)
}
}
I am using jquery file upload plugin for uploading files to my server but when server returns a json how to display that data without my page refreshing.I know that many posts says using iframe we can acheive i am very new to jquery and ajax can any figure it out and help me thank you in advance.
$('#fileupload').fileupload({
xhrFields: {withCredentials: true},
url: "fileUpload.do?",
type:"POST",
autoUpload: true,
formdata:{name:'FolderId',value:getfolderId()},
});
function getfolderId(){
var FolderId
alert();
$('#fileupload').on("click",function(){
FolderId=document.getElementById('currentFolder').value;
document.getElementById('selectedFolder').value = FolderId;
});
return FolderId;
}`
</form>`<form id="fileupload" on action="fileUpload.do" method="POST" enctype="multipart/form-data">
<div class="row fileupload-buttonbar">
<label for="form-upload">
<img src="htdocs/images/add_file.png"
style="width: 20px; height: 20px; border: 0" >
</label>
<input id="form-upload" type="file" name="upload" multiple style="opacity: 0; filter:alpha(opacity: 0);">
<im:hidden name="selectedFolder" id="selectedFolder" value="1" />
</div>
<div class="col-lg-5 fileupload-progress fade">
<div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar progress-bar-success" style="width:0%;"></div>
</div>
<div class="progress-extended"> </div>
</div>
</div>
<table role="presentation" class="table table-striped"><tbody class="files"></tbody></table>
</form>`
$('#addFile-link').bind("click",function(){
var FolderId
FolderId=document.getElementById('currentFolder').value;
document.getElementById('selectedFolder').value = FolderId;
if( FolderId==" " || FolderId==0){
$('#input').prop('disabled', true);
showFileMSg();
//alert("kindly select a folder to upload files");
}
else{
$('#input').prop('disabled', false);
$('#fileupload').fileupload({
xhrFields: {withCredentials: true},
url: "fileUpload.do?",
type:"POST",
dataType : "JSON",
autoUpload: true,
formdata:{name:'FolderId',value:FolderId},
disableImagePreview:true,
filesContainer: $('table.files'),
uploadTemplateId: null,
downloadTemplateId: null,
uploadTemplate: function (o) {
var rows = $();
$.each(o.files, function (index, file) {
var row = $('<tr class="template-upload fade">' +
'<td><span class="preview"></span></td>' +
'<td><p class="name"></p>' +
'<div class="error"></div>' +
'</td>' +
'<td><p class="size"></p>' +
'<div class="progress"></div>' +
'</td>' +
'<td>' +
(!index && !o.options.autoUpload ?
'<button class="start" disabled>Start</button>' : '') +
(!index ? '<button class="cancel">Cancel</button>' : '') +
'</td>' +
'</tr>');
row.find('.name').text(file.name);
row.find('.size').text(o.formatFileSize(file.size));
if (file.error) {
row.find('.error').text(file.error);
}
rows = rows.add(row);
});
return rows;
},
downloadTemplate: function (o) {
var rows = $();
$.each(o.files, function (index, file) {
var row = $('<tr class="template-download fade">' +
'<td><span class="preview"></span></td>' +
'<td><p class="name"></p>' +
(file.error ? '<div class="error"></div>' : '') +
'</td>' +
'<td><span class="size"></span></td>' +
'<td><button class="delete">Delete</button></td>' +
'</tr>');
row.find('.size').text(o.formatFileSize(file.size));
if (file.error) {
row.find('.name').text(file.name);
row.find('.error').text(file.error);
} else {
row.find('.name').append($('<a></a>').text(file.name));
if (file.thumbnailUrl) {
row.find('.preview').append(
$('<a></a>').append(
$('<img>').prop('src', file.thumbnailUrl)
)
);
}
row.find('a')
.attr('data-gallery', '')
.prop('href', file.url);
row.find('button.delete')
.attr('data-type', file.delete_type)
.attr('data-url', file.delete_url);
}
rows = rows.add(row);
});
return rows;
},
always:function (e, data) {
$.each( function () {
$(this).removeClass('fileupload-processing');
});
},
done: function (e, data) {
$('.template-upload').remove();
$.each(data.files, function (index, file) {
openFolder(FolderId);
});
},
error: function (jqXHR, textStatus, errorThrown) {
alert("jqXHR: " + jqXHR.status + "\ntextStatus: " + textStatus + "\nerrorThrown: " + errorThrown);
}
/*add: function (e, data) {
$('body').append('<p class="upl">Uploading...</p>')
data.submit();
},*/
})
}
});
I'm facing the problem that adding data to a subscribed Collection doesn't automatically refresh the shown elements of a collection. If I add a new element the element show's up for a second and then disappears! Refreshing the browser (F5) and the new element shows up.
I put the subscription into Meteor.autorun but things kept beeing the same.
lists.html (client):
<<template name="lists">
<div class="lists col-md-12" {{!style="border:1px red solid"}}>
<!-- Checklist Adder -->
<form id="list-add-form" class="form-inline" role="form" action="action">
<div class="col-md-6">
<input class="form-control" id="list-name" placeholder="Neue Liste" required="required"/>
</div>
<button type="submit" class="btn btn-primary" id="submit-add">
<span class="glyphicon glyphicon-plus-sign"></span>
Neue Liste
</button>
</form>
<!-- Checklist Ausgabe -->
<ul>
<br/>
{{#each lists}}
<li style="position: relative;" id="{{this._id}}" data-id="{{_id}}" class="clickOnList">
<!--<input type="button" class="deleteLists" id="{{this._id}}" value="-" style="z-index: 999;"/> -->
<span id="{{this._id}}" data-id="{{_id}}" style="padding-left: 10px; vertical-align:middle;">{{this.name}}</span>
<form id="changerForm_{{_id}}" class="changeList-name-form" data-id="{{_id}}" style="visibility: hidden; position: absolute; top:0;">
<input id="changerText_{{_id}}" type="text" class="list_name" data-id="{{_id}}" value="{{this.name}}" />
</form>
{{#if ownerOfList this._id}}
<a data-toggle="modal" class="userForListModal" id="{{this.name}}" data-id="{{this._id}}" data-target="#userForListModal">
<span class="glyphicon glyphicon-user" id="{{this.name}}" style="color:black;"data-id="{{this._id}}"></span><span style="color:black;" id="{{this.name}}" data-id="{{this._id}}" style="font-size: small; vertical-align: super">{{memberCount this._id}}</span></a>
<div class="deleteLists" id="dLBtn_{{_id}}" data-id="{{this._id}}" style="float: right; padding-right: 5px; padding-top: 1px; visibility: hidden;">
<span class="deleteLists glyphicon glyphicon-minus-sign" data-id="{{this._id}}"></span>
</div>
{{else}}
<a class="userForListModal">
<span class="glyphicon glyphicon-user" style="color:black;"></span><span style="color:black;" style="font-size: small; vertical-align: super">{{memberCount this._id}}</span></a>
{{/if}}
<!-- <button type="submit" class="deleteLists btn btn-default btn-xs" id="dLBtn_{{_id}}" data-id="{{this._id}}" style="float: right;" > -->
</button>
</li>
{{/each}}
</ul>
</div>
<div class="modal fade" id="userForListModal" >
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="userForListModalLabel"></h4>
</div></template>
<div class="modal-body col-md-12">
<div id="userForListModalUsers">
</div>
<p>Neuen Benutzer zur Checkliste hinzufügen:</p>
<form id="list-addUser-form" class="form-inline" role="form" action="action">
<div class="col-md-12">
<div class="col-md-6">
{{inputAutocomplete settings id="name-list-addUser" class="input-xlarge" placeholder="Benutzer Name" required="required"}}
</div>
<div class="col-md-6">
<button type="submit" class="btn btn-secondary" id="submit-list-addUser">
<span class="glyphicon glyphicon-plus-sign"></span>
Benutzer hinzufügen
</button>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<div id="userForListModalerrorMessage" style="color:red; display: none; text-align:left"></div><button type="button" class="btn btn-default" data-dismiss="modal">Schließen</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</template>
<template name="userPill">
<span class="label" style="color:black">{{username}}</span>
lists.js (client):
Template.lists.lists = function(){ return Lists.find(); }
Lists = new Meteor.Collection("lists");
Deps.autorun(function() {
Meteor.subscribe('lists');
})
lists.js
var activeListName = "";
var activeListID = "";
Template.lists.lists = function()
{
return Lists.find();
}
Template.lists.memberCount = function(id)
{
var count = "";
Meteor.call("listMemberCount", id, function(error,result)
{
if (error) {
console.log("List not initialized:" + error.reason);
}
else
{
Session.set("countMember_"+id,result);
}
});
return Session.get("countMember_"+id);
}
Template.lists.ownerOfList = function(id)
{
return ( Meteor.userId() == Lists.findOne({_id : id}).owner);
}
Template.lists.settings = function()
{
return {
position: "top",
limit: 5,
rules: [
{
token: '',
collection: Meteor.users,
field: "username",
template: Template.userPill
}]
}
}
Template.lists.events({
'submit #list-add-form' : function(e, t) {
/* Checklisten ausgeben */
e.preventDefault();
var name = t.find('#list-name').value;
var id = new Meteor.Collection.ObjectID().valueOf();
var id_block = new Meteor.Collection.ObjectID().valueOf();
Lists.insert({_id : id, name : name, owner : Meteor.userId()});
Meteor.users.update({_id : Meteor.userId()}, {$addToSet :{lists : id}});
Listitems.insert({_id : id_block, name : "", items: []});
Lists.update(id, {$addToSet : {items : id_block}});
},
'click .clickOnList' : function(e)
{
/* Eventhandler fuer klick auf Checkliste */
Session.set("activeListId", e.target.id);
$("#"+e.target.id).siblings('li').removeClass("active");
$("#"+e.target.id).addClass("active");
},
'mouseover .clickOnList' : function (e,t) {
$( ".deleteLists" ).each(function( index, item ) {
if ( item.getAttribute("data-id") == e.target.getAttribute("data-id")) {
item.style.visibility = 'visible';
} else {
item.style.visibility = 'hidden';
}
});
},
'mouseleave .clickOnList' : function (e,t) {
$( ".deleteLists" ).each(function( index, item ) {
item.style.visibility = 'hidden';
});
},
'click .deleteLists' : function(e, t)
{
/* Eventhandler zum loeschen einer Checkliste */
var id = e.target.getAttribute("data-id");
Meteor.call("removeList", id);
console.log("test");
},
'click .changeListnameButton' : function(e,t) {
var id = e.target.getAttribute("data-id");
document.getElementById("changerForm_" + id).style.visibility = 'visible';
document.getElementById(id).style.visibility = 'hidden';
document.getElementById("changerText_" + id).focus();
},
'dblclick .clickOnList' : function(e,t){
var id = e.target.getAttribute("data-id");
document.getElementById("changerForm_" + id).style.visibility = 'visible';
document.getElementById(id).style.visibility = 'hidden';
document.getElementById("changerText_" + id).focus();
},
'submit .changeList-name-form' : function(e,t) {
e.preventDefault();
var id = e.target.getAttribute("data-id");
var text = document.getElementById("changerText_" + id).value;
if(text != '') {
Meteor.call("changeListName", id, text);
}
if (Session.get("activeListId", e.target.id) == id ) {
Session.set("activeListName", text);
}
document.getElementById("changerForm_" + id).style.visibility = 'hidden';
document.getElementById(id).style.visibility = 'visible';
},
'blur .list_name' : function(e,t) {
e.preventDefault();
var id = e.target.getAttribute("data-id");
var text = document.getElementById("changerText_" + id).value;
if((text != '') && (document.getElementById(id).style.visibility == 'hidden')) {
Meteor.call("changeListName", id, text);
}
if (Session.get("activeListId", e.target.id) == id ) {
Session.set("activeListName", text);
}
document.getElementById("changerForm_" + id).style.visibility = 'hidden';
document.getElementById(id).style.visibility = 'visible';
},
'click .userForListModal' : function(e,t) {
e.preventDefault();
activeListName = e.target.id;
activeListID = e.target.getAttribute("data-id");
//console.log(activeListID + " " + activeListName);
//console.log("New user for Liste" + Lists.findOne({_id : activeListID}).name);
userForList(activeListID);
$("#userForListModalLabel").html("Benutzer der Liste '"+ activeListName+ "'");
},
'submit #list-addUser-form' : function(e,t) {
e.preventDefault();
var newUser = $('#name-list-addUser').val();
Meteor.call("addUserToList", newUser, activeListID, function(error,result)
{
if (error) {
console.log(error.reason);
}
else
{
if (result == 1) {
$('#userForListModalerrorMessage').fadeIn(1000, function() {$(this).delay(1000).fadeOut(1000);});
$('#userForListModalerrorMessage').html("<div class=\"alert alert-danger\">Benutzer wurde nicht gefunden...</div>");
}
else if (result == 2) {
$('#userForListModalerrorMessage').fadeIn(1000, function() {$(this).delay(1000).fadeOut(1000);});
$('#userForListModalerrorMessage').html("<div class=\"alert alert-warning\">Benutzer ist Besitzer der Liste...</div>");
}
}
});
}
});
function userForList(id)
{
try
{
var owner = Lists.findOne({_id : id}).owner;
var members = Lists.findOne({_id : id}).member;
}
catch(e){
}
output = "<ul>";
output += "<li> Besitzer der Liste: <ul><li>" + owner + "</li></ul></li>";
output += "<li>Mitarbeiter der Liste: <ul>"
if (members != undefined) {
for(i=0; i<members.length; i++)
{
output+= "<li>" + members[i] + "</li>";
}
}
output += "</ul></li></ul>";
$('#userForListModalUsers').html(output);
}
main.js (server):
Lists = new Meteor.Collection("lists");
Meteor.publish("lists", function(){
var ListsOfUser = Meteor.users.findOne({_id : this.userId}).lists;
return Lists.find({_id :{ $in : ListsOfUser}});
});
Lists.allow({
insert : function(userID, list)
{
return (userID && (list.owner === userID));
},
//todo
update : function(userID)
{
return true;
},
//todo
remove : function(userID)
{
return true;
}
});
Thanks in advance!
I believe this is happening because the ListsOfUser variable in your Meteor.publish "lists" function is not a reactive data source. ListsOfUser is an array drawn from your result set, not a reactive cursor. Therefore it is not being invalidated server side when a user adds a new list on the client. From the Meteor docs (note the last sentence especially):
If you call Meteor.subscribe within a reactive computation, for example using
Deps.autorun,the subscription will automatically be cancelled when the computation
is invalidated or stopped; it's not necessary to call stop on subscriptions made
from inside autorun. However, if the next iteration of your run function subscribes
to the same record set (same name and parameters), Meteor is smart enough to skip a
wasteful unsubscribe/resubscribe.
ListsOfUser is not changing when a user adds a new list, so you are not being unsubscribed and resubscribed to the lists publication. (Note also that Meteor.users.findOne() is also not a reactive data source - you might want to switch it to Meteor.users.find() depending on how you go about making ListsOfUser reactive).
There are a few ways you could go about making the user lists reactive.
First, you could publish both the user cursor and the lists cursor, either separately or as an array in the same publish function, place both subscriptions in your Deps.autorun, and then fish out the user lists client side in a helper.
Meteor.publish("userWithLists", function(){
return Meteor.users.find(
{_id: this.userId},
{fields: {'lists': 1}}
);
});
Second, you could publish the static array of user lists as its own Collection and then use cursor.observe or cursor.observeChanges to track when it changes. While my understanding is that this is closest to the "correct" or "Meteor" way of doing it, it is also apparently quite verbose and I have not tried it. This tutorial goes into great detail about how you would tackle something like this: https://www.eventedmind.com/feed/aGHZygsphtTWELpKZ
Third, you could simply stick the user lists into your Session object, which is already reactive, and then publish your Lists.find() based on the Session, i.e.:
Meteor.publish("lists", function(lists){/* find code goes here */});
and
Deps.autorun(function(){
Meteor.subscribe("lists", Session.get("listsOfUser"));
});
This last one is probably an overuse / abuse of the Session object, particularly if your listsOfUser grows large, but should work as a hack.
I know this question is old, but still someone might be seeking for the answer. And the answer is Meteor.publishComposite available with a publish-composite package - https://atmospherejs.com/reywood/publish-composite
And there's an example there
Meteor.publishComposite('topTenPosts', {
find: function() {
// Find top ten highest scoring posts
return Posts.find({}, { sort: { score: -1 }, limit: 10 });
},
children: [
{
find: function(post) {
// Find post author. Even though we only want to return
// one record here, we use "find" instead of "findOne"
// since this function should return a cursor.
return Meteor.users.find(
{ _id: post.authorId },
{ limit: 1, fields: { profile: 1 } });
}
},
{
find: function(post) {
// Find top two comments on post
return Comments.find(
{ postId: post._id },
{ sort: { score: -1 }, limit: 2 });
},
children: [
{
find: function(comment, post) {
// Find user that authored comment.
return Meteor.users.find(
{ _id: comment.authorId },
{ limit: 1, fields: { profile: 1 } });
}
}
]
}
]
});
I am rather new to meteor but, in your server code, should it not be:
var ListsOfUser = Meteor.users.findOne({_id : Meteor.userId}).lists;
rather than:
var ListsOfUser = Meteor.users.findOne({_id : Meteor.userId}).lists;