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?
Related
there is a way to listen the laravel livewire lifecycle hooks? for example...
in php is:
public function updatedFoo($value)
{
//
}
how it can be in js (i know use #this generate the id finder)?
window.Livewire.find('componentIdGenerated').on('updatedFoo', function(value) {
//
});
thanks a lot!
It's possible and really cool. As the documentation tells, there is JavaScript hooks related like
<script>
document.addEventListener("DOMContentLoaded", () => {
....
Livewire.hook('message.sent', (message, component) => {})
Livewire.hook('message.failed', (message, component) => {})
Livewire.hook('message.received', (message, component) => {})
Livewire.hook('message.processed', (message, component) => {})
});
</script>
Let's say, you make some call to some method and using this you can get messages hooks and do proper operations
<script>
document.addEventListener("DOMContentLoaded", () => {
Livewire.hook('message.sent', (message,component) => {
if (message.updateQueue[0].payload.method === 'openModal') {
// message was sent
}
Livewire.hook('message.received', (message,component) => {
if (message.updateQueue[0].payload.method === 'openModal') {
// message was received
}
// and go on!
</script>
also you can listen when an event occurs and do the same
<script>
document.addEventListener("DOMContentLoaded", () => {
Livewire.hook('message.sent', (message,component) => {
if (message.updateQueue[0].payload.event === 'someDispatchedEvent') {
// message was sent
}
Livewire.hook('message.received', (message,component) => {
if (message.updateQueue[0].payload.event === 'someDispatchedEvent') {
// message was received
}
// and go on!
</script>
hope you can exploit this more and show us how you go! ;-)
thanks to #Prospero for give the first steps and the general idea about the necesary code
first we need to save the initial state of our property (in my case is a modal component, the id is dynamic):
The variables in brackets are blade variables)
...
#php
$id = $id ?? \Illuminate\Support\Str::random(15);
$model = $attributes->wire('model')->value()
#endphp
...
<script wire:ignore>
var model{{ $id }};
document.addEventListener("livewire:load", function(event) {
model{{ $id }} = #this.{{ $model }};
});
</script>
then, with the livewire hooks, you have to listen the element.updated event, and compare the initial state with the new state of the livewire property
i use only conditional comparisons, i saw you can use Proxy for clean code:
<script wire:ignore>
document.addEventListener("DOMContentLoaded", () => {
Livewire.hook('element.updated', (el, component) => {
if(model{{ $id }} && !#this.{{ $model }})
new bootstrap.Modal(document.getElementById('{{ $id }}')).hide();
if(!model{{ $id }} && #this.{{ $model }})
new bootstrap.Modal(document.getElementById('{{ $id }}')).show();
model{{ $id }} = #this.{{ $model }};
});
});
</script>
I use this for open a bootstrap modal blade component component.
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.
My thought process:
When the show page opens, get the article's id with JavaScript.
Check this id exist or not in cookie
If not exists, write it into cookie and send an ajax request, the backend updates view times.
If exists, do nothing.
Demo:
View:
<div class="card">
<div class="card-block text-xs-center">
<h5 class="card-title">{{$article->title}}</h5>
<hr class="m-y-2">
<h6 class="card-subtitle text-muted">date:{{$article->created_at->format('Y-m-d')}}
views:{{$article->view_times}}</h6>
</div>
<div class="card-block">
<p class="card-text">{{$article->content}}</p>
</div>
</div>
Controller:
class ArticlesController extends Controller
{
//`show` method
public function show($id)
{
$article = Article::findOrFail($id);
return view('show', compact('article'));
}
//the method of updating view times.
public function statistics(Request $request)
{
$id = $request->input('id');
$article = Article::findOrFail($id);
$view_time=$article->view_time;
$article->view_time=$view_time+1;
$article->save();
}
}
JavaScript:
Vue.http.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name=csrf-token]').getAttribute('content')
Vue.http.options.emulateJSON = true;
var vm = new Vue({
el: "body",
data: function(){
return{
id:[]
}
},
created() {
//1、Get the article's id.Do I have to send an ajax request? Is there any other way?
this.$http.get('article/get-id').then((response) => {
// success callback
this.id=response.data;
}, (response) => {
// error callback
});
//2、After Getting the `id`,check it in cookie,I don't know how to do it?
//3、If not exists,write it into cookie and send an ajax request,how to write the if() sentence?
if(){
var formData = new FormData();
var id=this.id;
formData.append('id',id);
this.$http.patch('article/statistics', formData,{
before(request) {
if (this.previousRequest) {
this.previousRequest.abort();
}
this.previousRequest = request;
}
}).then((response) => {
// success callback
}, (response) => {
// error callback
});
}
}
});
Questions:
There are three questions, shown as comments in JavaScript code above.
Why function return full html code in console. How return only data(div block)?
HTML
How return data only this block
<div class="message_box">
#foreach(json_decode($message) as $content)
{{ $content->message }}
#endforeach
</div>
AJAX
$(function () {
$.get('chat', function(data){
console.log(data);
});
});
Controller
public function chat()
{
$user = User::all();
$message = MessageModel::orderBy('id')->get();
return view('chat.chat', ['user' => $user, 'message' => json_encode($message)]);
}
Result prtscreen here
This problem occurs when you extend layout
If you are using Laravel 5 you should check in your view if you have any #extend directive in blade file or if you are using Laravel 4 you should also check in your controller that if you have any $layout property
Here you are returning html
return view('chat.chat', ['user' => $user, 'message' => json_encode($message)]);
Now if you want to return this as JSON response you have render the HTML & then return the response as an array like this
return ['html' => view('chat.chat', ['user' => $user, 'message' => json_encode($message)])->render()];
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'
});
}