I am attempting to make a element in a meteor template editable via a update function. The data changes when it is inserted from a server side code in the fixture.js code. However I have no luck updating it via a editable form with some Template.name.events({}); code and, creating a collection, publishing and subscribing to it. The very last piece of code is the fixture.js file. So in some regard I can insert into the collection and update it, but I have no luck with the edit financialsEdit template. The router.js file I included only contains parts regarding the financials template. If needed I will post more.
Basically I can't update a collection value with a update function using $set and passing a key value pair.
UPDATE: I added the permissions.js file in the lib directory to show what ownsDocument returns.
Here is my code.
client Directory
client/editable/edit_financial.js
Template.financialsEdit.events({
'submit .financialsEdit': function(e) {
e.preventDefault();
var currentFinanceId = this._id;
var financialsProperties = {
issuedOutstanding: $('#issuedOutstanding').val()
}
Financials.update(currentFinanceId, {$set: financialsProperties}, function(error) {
if (error) {
alert(error.reason);
} else {
console.log(financialsProperties);
// Router.go('financials');
Router.go('financials');
}
});
}
});
client/editable/financials_helpers.js
Template.financials.helpers({
financials: function() {
return Financials.find();
},
ownFinancial: function() {
return this.userId === Meteor.userId();
}
});
client/editable/financials
<template name="financials">
<div id="finance">
{{#each financials}}
<h2>Issued Outstand : {{issuedOutstanding}}</h2>
{{/each}}
</div>
</template>
client/editable/financials_edit.html
<template name="financialsEdit">
<form class="main form financialsEdit">
<input id="issuedOutstanding" type="number" value="{{issuedOutstanding}}" placeholder="{{issuedOutstanding}}" class="form-control">
<input type="submit" value="Submit" class="submit"/>
</form>
</template>
lib Directory
lib/router.js
Router.route('/financials', function () {
this.render('financials');
});
Router.route('/financialsedit', {name: 'financialsEdit'});
lib/collections/financials.js
Financials = new Mongo.Collection('financials');
Financials.allow({
update: function(userId, financial) { return ownsDocument(userId, financial); },
remove: function(userId, financial) { return ownsDocument(userId, financial); },
});
Financials.deny({
update: function(userId, financial, fieldNames) {
// may only edit the following two fields:
return (_.without(fieldNames, 'issuedOutstanding').length > 0);
}
});
lib/permissions.js
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
server/publications.js
Meteor.publish('financials', function() {
return Financials.find();
});
server/fixture.js
if (Financials.find().count() === 0) {
Financials.insert({
issuedOutstanding: '43253242'
});
}
Related
I'm trying to build a simple website builder that allow users to save their generated html created with Vue component and see it at a certain URL.
Because of it I have to store and retrieve the html generated but I have some problems with retrieving of the code. Here is my step:
When user click "save" this function is fired, that select the portion of HTML that include the "website" built by the user:
saveBuilders: function () {
let pages = [];
let builders = $('[id*="builder-container-"]');
$.each(builders, function (key, builder) {
let singleElem = $(builder).attr('id');
pages.push(clearElement.html());
});
this.storeInDb(pages);
},
storeInDb: function (pagesList) {
axios.post("/landing-page/store", {
name: this.name,
description: this.description,
html: pagesList
})
.then(function (response) {
console.log('Cool');
})
.catch(function (error) {
console.log('ERROR', error.response);
});
},
The Axios request is handled by this function that store the html portion in DB
public function store(Request $request)
{
$data = $request->all();
$html = $data['html'];
$landingPage = new LandingPage();
$landingPage->name = $data['name'];
$landingPage->description = $data['description'];
$landingPage->user_id = Auth::user()->id;
$landingPage->html = json_encode($html);
try {
$landingPage->save();
return 'true';
} catch (exception $e) {
return $e;
}
}
Now when the user visit a certain URL, for keep thing simple suppose is example.it/website/0, this function is fired:
public function show($landing_id)
{
try {
$landingPage = LandingPage::where([
'id' => $landing_id,
'user_id' => Auth::user()->id
])->first();
} catch (\Exception $e) {
$landingPage = null;
}
if ($landingPage != null) {
//GET THE HTML
$page = json_decode($landingPage->html);
return view('landing_page.show')->with('page', $page)
} else {
abort(404, 'Error');
}
}
And this the blade where I'm trying to re-create the Vue.js environment
<body>
<span id="countdown"></span>
<div id="builder-pagina">
<builder>
{!! $page !!}}
</builder>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="{{asset('js/landing_page/app.js')}}"></script>
</body>
</html>
I thought that having the html generated by vue similar to something like that into the DB...
<div data-v-29b64d26="" >
<h1>This piece of code was stored into my DB</h1>
<div data-v-56f62f0a="">
</div>
</div>
...you could create everything working simply by pasting the code and by using the same js file used for compiling vue.js.
I've tried pass the entire code by props but is not working. Also tried with slot. Any suggestions?
I have used List.JS before successfully, but this time I'm trying to use it with a Vue.JS rendering of a list from JSON data.
I have a button at the top that when clicked should show only the QB position player.
Unfortunately I just get nothing, all list items are removed and I don't get an error in the console so I'm not sure how to diagnose this.
Could it have something to do with the fact that the list elements aren't prerendered/static html but injected using vue.js?
https://jsfiddle.net/nolaandy/hw2mheem/
HTML/Vue Template
<div id='app'>
<div class="all-players-wrapper" id="all-player-listings">
<button id="filter-qb">QB</button>
<ul class="list">
<li v-for="player in playerJSON">
<div class="player-listing">
<div class="player-left">
<div class="player-name">{{player.firstName}} {{player.lastName}}</div>
<div class="playerPosition">{{ player.Position }}</div>
</div><!-- end player-left -->
<div class="player-right">
<div class="player-grade">GRADE <span>{{player.NFLGrade}}</span></div>
</div> <!--end player-right -->
</div>
</li>
</ul>
</div>
</div>
JS
var vm = new Vue({
el: '#app',
data: {
status: 'Combine Particpants',
playerJSON: []
},
created: function () {
this.loadData();
},
methods: {
loadData: function () {
var self = this;
axios.get('https://s3-us-west-2.amazonaws.com/s.cdpn.io/500458/tiny.json').then(function (response) {
self.playerJSON = response.data
console.log(response.data);
})
.catch(function (error) {
self.status = 'An error occurred - ' + error
});
}
}
});
var options = {
valueNames: [ 'playerPosition' ]
};
var featureList = new List('all-player-listings', options);
$('#filter-qb').click(function() {
featureList.filter(function(item) {
if (item.values().playerPosition == "QB") {
return true;
} else {
return false;
}
});
return false;
});
As you suspected, List.js isn't going to work properly if the DOM changes unpredictably. In this case, axios makes its call and populates the data after the (empty) List has been read into featureList.
Your example would work if you put the list-selecting-and-filtering code in the resolution of the axios call, but that's not going to be a solution that works in a truly dynamic environment.
A custom directive will be called every time the DOM updates, so you can apply your adjustments consistently. Here's a directive to apply a filter using List.js:
directives: {
filteredList(el, binding) {
if (binding.value) {
const options = {
valueNames: ['playerPosition']
};
const featureList = new List(el, options);
featureList.filter((item) => item.values().playerPosition === binding.value);
}
}
}
Apply it like so:
<div class="all-players-wrapper" v-filtered-list="filterValue">
Add the filterValue data item, and have the button set it:
<button id="filter-qb" #click="() => filterValue='QB'">QB</button>
and you're in business.
It's worth noting that you could get the same effect by using a computed to filter the data, and you wouldn't need an external library.
Updated fiddle
i have problem. When I click the button, it receives an entire database, but I want laod part database. How can I do this?
For example: After every click I would like to read 10 posts.
Thx for help.
Messages.vue:
<div class="chat__messages" ref="messages">
<chat-message v-for="message in messages" :key="message.id" :message="message"></chat-message>
<button class="btn btn-primary form-control loadmorebutton" #click="handleButton">Load more</button>
</div>
export default{
data(){
return {
messages: []
}
},
methods: {
removeMessage(id){...},
handleButton: function () {
axios.get('chat/messagesmore').then((response) => {
this.messages = response.data;
});
}
},
mounted(){
axios.get('chat/messages').then((response) => {
this.messages = response.data
});
Bus.$on('messages.added', (message) => {
this.messages.unshift(message);
//more code
}).$on('messages.removed', (message) => {
this.removeMessage(message.id);
});
}
}
Controller:
public function index()
{
$messages = Message::with('user')->latest()->limit(20)->get();
return response()->json($messages, 200);
}
public function loadmore()
{
$messages = Message::with('user')->latest()->get();
// $messages = Message::with('user')->latest()->paginate(10)->getCollection();
return response()->json($messages, 200);
}
paginate(10) Loads only 10 posts
You can do it like this:
<div class="chat__messages" ref="messages">
<chat-message v-for="message in messages" :key="message.id" :message="message"></chat-message>
<button class="btn btn-primary form-control loadmorebutton" #click="handleButton">Load more</button>
</div>
export default{
data(){
return {
messages: [],
moreMessages: [],
moreMsgFetched: false
}
},
methods: {
removeMessage(id){...},
handleButton: function () {
if(!this.moreMsgFetched){
axios.get('chat/messagesmore').then((response) => {
this.moreMessages = response.data;
this.messages = this.moreMessages.splice(0, 10);
this.moreMsgFetched = true;
});
}
var nextMsgs = this.moreMessages.splice(0, 10);
//if you want to replace the messages array every time with 10 more messages
this.messages = nextMsgs
//if you wnt to add 10 more messages to messages array
this.messages.push(nextMsgs);
}
},
mounted(){
axios.get('chat/messages').then((response) => {
this.messages = response.data
});
Bus.$on('messages.added', (message) => {
this.messages.unshift(message);
//more code
}).$on('messages.removed', (message) => {
this.removeMessage(message.id);
});
}
}
-initialize a data property morMsgFetched set to false to indicate if more messages are fetched or not
if morMsgFetched is false make the axios request and st the response to moreMessages, then remove 10 from moreMessages and set it to messages[]..
After that set morMsgFetched to true
on subsequest click remove 10 from moreMessages and push it to 'messages[]`
Use Laravels built in pagination.
public function index()
{
return Message::with('user')->latest()->paginate(20);
}
It returns you next_page url which you can use to get more results calculated automatically
This might be too late but i believe the best way to do it is using pagination, Initially onMounted you'll send a request to let's say /posts?page=1, the one is a variable let's say named 'pageNumber', each time the user clicks on the "Load More" button, you'll increment the pageNumber and resent the request, the link will page /posts?page=2 this time, at this point you can append the results you've got to the already existing one and decide if the Load More button should be shown based on the last_page attribute returned by laravel paginator...
I'm sure you already solved your problem or found another alternative, this might be usefull for future developers.
I want to create a little admin frontend on which I can display a list of all users and to edit user-data (like roles).
So what I did so far is to create a new publication (allUsers) and create methods for displaying and changeing the roles of the user:
shared/collections.js
Users = Meteor.users;
server.js
Meteor.methods({
'updateRole': function(userId, role, value) {
if (value) Roles.addUsersToRoles(userId, role);
else Roles.removeUsersFromRoles(userId, role);
return userId;
},
'loadRoles': function(userId, role) {
var result = { role: role, value: Roles.userIsInRole(userId, role) };
return result;
}
});
Meteor.publish('allUsers', function(){
return Users.find();
});
router.js
Router.route('/users', {
name: 'users',
waitOn: function () {
Meteor.subscribe('allUsers');
},
data: function() {
return {
users: Users.find({})
}
}
});
Router.route('/user/:_id', {
name: 'userEdit',
waitOn: function () {
Meteor.subscribe('allUsers');
Session.set("userEditId", this.params._id);
},
data: function () {
return {
users: Users.findOne({ _id: this.params._id })
};
}
});
client.js
Template.userEdit.onRendered(function() {
var role,
userId = Session.get("userEditId"),
elements = document.getElementById('formRoles').elements;
for(var i = 0; i < elements.length; i++) {
var dom = elements[i];
if(dom.getAttribute('type') == 'checkbox') {
role = dom.getAttribute('name');
Meteor.call('loadRoles', userId, role, function (error, result) {
$("input[name=" + result.role + "]").prop('checked', result.value);
});
}
}
});
Template.userEdit.events({
'change input[type="checkbox"]': function(event) {
var userId = document.getElementById('formRoles').getAttribute('data-id'),
role = event.target.getAttribute('name');
value = event.target.checked,
Meteor.call('updateRole', userId, role, value, function (error, result) {
console.log(result);
});
}
});
template
<template name="users">
<ul class="list">
{{#each users}}
<li>{{profile.name}}, {{profile.surname}}</li>
{{/each}}
</ul>
</template>
<template name="userEdit">
<h1>{{users.profile.name}}, {{users.profile.surname}}</h1>
{{#if isInRole 'admin'}}
<form id="formRoles" data-id="{{users._id}}">
<label><input type="checkbox" name="admin"> Administrator</label>
<label><input type="checkbox" name="role2"> Role</label>
<label><input type="checkbox" name="role3"> Role</label>
<label><input type="checkbox" name="role4"> Role</label>
<label><input type="checkbox" name="role5"> Role</label>
</form>
{{/if}}
</template>
Update
This is how I display all users in a list and display all roles of a selected user. The problem is, that the display works in two steps with a delay:
That means, when the page is loaded, first there is just one user and after a second all other users are added to the list. Same to a userEdit: First I just see "," and after a second it is filled to "Name, Firstname".
Can somebody explain this behaviour to me? And how can I improve the code to avoid these two steps?
Because of the "two-step-display", I think I wrote the code much to complicated...
Update 2
I think the delay results from the subscription of allUsers in the route-waitOn. Is it possible to improve that?
I'm using CollectionFS to allow image uploads. The image uploads need to belong to specific posts. I followed the steps from the documentation - Storing FS.File references in your objects - however, I'm having a hard time displaying the image of the associated post.
The post currently saves with a postImage that references an image._id - this part is working fine. However, I am unsure how to display the actual photo, as it will need to grab the photo from the images collection (the post collection just saves an ID to reference).
post-list.html
<template name="postList">
<tr data-id="{{ _id }}" class="{{ postType }}">
...
<td><textarea name="postContent" value="{{ postContent }}"></textarea> </td>
<td>{{ postImage._id }} </td> // This currently displays the correct image._id, but I would like to display the image,
<td><button class="delete tiny alert">Delete</button></td>
</tr>
</template>
post-list.js
Template.postList.helpers({
posts: function() {
var currentCalendar = this._id;
return Posts.find({calendarId: currentCalendar}, {sort: [["postDate","asc"]] });
}
});
post-form.js - This form creates a new Post and Image. The Image._id is saved to the Post.postImage.
Template.postForm.events({
// handle the form submission
'submit form': function(event) {
// stop the form from submitting
event.preventDefault();
// get the data we need from the form
var file = $('.myFileInput').get(0).files[0];
var fileObj = Images.insert(file);
var currentCalendar = this._id;
var newPost = {
...
calendarId: currentCalendar,
owner: Meteor.userId(),
postImage: fileObj,
};
// create the new poll
Posts.insert(newPost);
}
});
use reywood:publish-composite and dburles:collection-helpers so;
Collections || collections.js
Posts = new Mongo.Collection('posts');
Images = new FS.Collection("files", {
stores: [
// Store gridfs or fs
]
});
Posts.helpers({
images: function() {
return Images.find({ postId: this._id });
}
});
Template || template.html
<template name="postList">
{{# each posts }}
{{ title }}
{{# each images }}
<img src="{{ url }}">
{{/each}}
{{/each}}
</template>
Client || client.js
Template.postList.helpers({
posts: function() {
return Posts.find();
}
});
Template.postList.events({
// handle the form submission
'submit form': function(event, template) {
// stop the form from submitting
event.preventDefault();
// get the data we need from the form
var file = template.find('.myFileInput').files[0];
Posts.insert({
calendarId: this._id,
owner: Meteor.userId()
}, function(err, _id) {
var image = new FS.File(file);
file.postId = _id;
if (!err) {
Images.insert(image);
}
});
}
});
Router || router.js
Router.route('/', {
name: 'Index',
waitOn: function() {
return Meteor.subscribe('posts');
}
});
Server || publications.js
Meteor.publishComposite('posts', function() {
return {
find: function() {
return Posts.find({ });
},
children: [
{
find: function(post) {
return Images.find({ postId: post._id });
}
}
]
}
});
When using CollectionFS, on the client side you need to ensure that your subscriptions are correctly defined. This is the biggest stumbling block i've encountered with my developers in using CFS - understanding and mapping the subscription correctly.
First things first - you need to have a subscription that is going to hit Images collection. I'm not familiar with the latest CFS updates for their internal mapping but the following approach has usually worked for me.
Meteor.publish('post', function(_id) {
var post = Posts.findOne({_id: _id});
if(!post) return this.ready();
return [
Posts.find({_id: _id}),
Images.find({_id: post.postImage})
]
});
For displaying, there is a handy CFSUI package( https://github.com/CollectionFS/Meteor-cfs-ui ) that provides some nice front end handlers.
With the above mapping your subscription, you can then do something like
{{#with FS.GetFile "images" post.postImage}}
<img src="{{this.url store='thumbnails'}}" alt="">
{{/with}}