How to sort object by key value in javascript? - javascript

I'm making a code to fetch content from contentful using AJAX. I've success retrieve data and display it, but something is not quite what I want. Because the content that I get is not in the same order as the contentful cms, so I add another field called sequence. So in my code I added a sort() and Object.keys() function before forEach(), but there is no error and data not appears ,does anyone know why data not appears?
If you want to try debugging, you can look at This Codepen.
function renderContentBySection(sectionName, appendElement, numberOfSkeleton, elementAttribute, elementClass){
$.ajax({
url : 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function(data){
const getData = data.fields
if(getData[sectionName]) {
if(getData[sectionName] && getData[sectionName].length) {
getData[sectionName].forEach((item, index) => {
getSingleEntry(item.sys.id)
});
}
}
}
});
}
function getSingleEntry(contentId){
$.ajax({
url : `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${contentId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function(dataKat){
getAssetData(dataKat.fields.image.sys.id, dataKat.fields.sequence)
$('.data-banner').append(JSON.stringify(dataKat.fields, null, 4))
$('.data-banner').append('<br>');
}
});
}
function getAssetData(assetsId, sequenceId){
$.ajax({
url : `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/assets/${assetsId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function(getAssetsData){
$('.data-image').append(JSON.stringify(getAssetsData.fields, null, 4))
$('.data-image').append('<br>');
}
});
}
$(document).ready(function(){
renderContentBySection('mainBannerImage', '#carousel-inner', 1, 'banner', 'main-banner-item');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<pre class="data-banner">
<h4>Get Data Main Banner:</h4>
</pre>
<br>
<pre class="data-image">
<h4>Get Data for Each Image in Main Banner:</h4>
</pre>

Because you completely changed the criteria, I will provide an answer for your second ask.
The key to working with multiple batches of asynchronous requests is to gather all the requests, and then listen for them all to complete. Then, do the same thing again with the next batch of requests.
Otherwise, your HTML will print in the order the responses are returned and it will seem random.
Once you have gathered all the completed requests, you can sort() them, then do a forEach through them.
function listenForEntries(arrAllEntryRequests) {
//set up a listener for when all requests have finished, using "spread operator" (...) to send all requests as parameters to when():
$.when(...arrAllEntryRequests).done(
//done:
function (...arrAllEntryResponses) {
let arrAllEntries = [];
//console.log("arrAllEntryResponses", arrAllEntryResponses);
arrAllEntryResponses.forEach((e) => {
arrAllEntries.push(e[0].fields);
});;
//all images loaded, sort:
arrAllEntries.sort((a, b) => (a.sequence < b.sequence ? -1 : 1));
//sorting done, get asset data for each. This is also asyncronous so you need to do the same thing as above:
let arrAllAssetRequests = [];
arrAllEntries.forEach((entryData) => {
//console.log("entryData", entryData);
$('.data-sequence').append(`
<ul>
<li>
Sequence ID: ${entryData.sequence}<br>
Title Banner: ${entryData.title}
</li>
</ul>`)
let assetReqObj = getAssetData(entryData.image.sys.id, entryData.sequence);
arrAllAssetRequests.push(assetReqObj);
});
listenForAssets(arrAllAssetRequests);
}
);
}
function listenForAssets(arrAllAssetRequests) {
$.when(...arrAllAssetRequests).done(
//done:
function (...arrAllAssetResponses) {
//all assets loaded, sort:
arrAllAssetResponses.sort((a, b) => (a[2].sequence < b[2].sequence ? -1 : 1));
arrAllAssetResponses.forEach((assetData) => {
//console.log("assetData", assetData);
if(assetData.length > 0) {
$('.data-assets').append(`
<ul>
<li>
Sequence ID: ${assetData[2].sequence}<br>
Title Banner: ${assetData[0].fields.title}<br>
<img class="img-fluid" src="${assetData[0].fields.file.url}">
</li>
</ul>`);
} else {
console.error("Something wrong with assetData", assetData);
}
});
}
);
}
function renderContentBySection(sectionName, appendElement, numberOfSkeleton, elementAttribute, elementClass) {
$.ajax({
url: 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function (data) {
const getData = data.fields
//move array to inside this function as it's the only place it will be used:
let arrAllEntryRequests = [];
if (getData[sectionName]) {
if (getData[sectionName] && getData[sectionName].length) {
getData[sectionName].forEach((item, index) => {
arrAllEntryRequests.push(getSingleEntry(item.sys.id));
});
//once array of requests is created, listen for it to finish:
listenForEntries(arrAllEntryRequests);
}
}
}
});
}
function getSingleEntry(contentId) {
return $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${contentId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (dataKat) {
//do nothing
}
});
}
function getAssetData(assetsId, sequenceId) {
let $xhr = $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/assets/${assetsId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (assetData) {
//do nothing
}
});
$xhr.sequence = sequenceId; //store the sequence for later
return $xhr;
}
$(document).ready(function () {
renderContentBySection('mainBannerImage', '#carousel-inner', 1, 'banner', 'main-banner-item');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container-fluid">
<div class="row">
<div class="col-6">
<div class="data-sequence">
<span> This is sequence data:</span>
</div>
</div>
<div class="col-6">
<div class="data-assets">
<span> This is assets data:</span>
</div>
</div>
</div>
</div>

Because your data is loaded asyncronously, you will need to create a queue of your requests, and listen for them to all finish.
I have commented my code below so you can understand how it works.
First, you need to use the spread operator a lot ..., to work with an unknown number of array elements.
(https://stackoverflow.com/a/35169449/1410567)
Second, you need to use $.when(...array).done(function(...results) { to know when the requests have finished.
(https://blog.kevinchisholm.com/javascript/jquery/using-jquery-deferred-to-manage-multiple-ajax-calls/)
Third, you need to use Array.sort() to sort the array of objects, comparing their sequence, and returning 1 or -1 to sort them.
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
//create an empty array to hold the queue:
let allImageRequests = [];
function listenForImages() {
//set up a listener for when all requests have finished, using "spread operator" (...) to send all requests as parameters to when():
$.when(...allImageRequests).done(
//done:
function (...arrAllImagesResp) {
let arrAllImages = [];
console.log("arrAllImagesResp", arrAllImagesResp);
arrAllImagesResp.forEach((e) => {
console.log(e);
arrAllImages.push(e[0].fields);
});;
//all images loaded, sort:
arrAllImages.sort((a, b) => (a.sequence < b.sequence ? -1 : 1));
console.log("done", arrAllImages);
//sorting done, display results:
$('.data-image').append("\n\n<strong>All Images Sorted:</strong> \n\n" + JSON.stringify(arrAllImages, null, 4));
}
);
}
$.ajax({
url: 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function (data) {
console.log("got data", data);
const getData = data.fields.mainBannerImage
$('.data-banner').append(JSON.stringify(getData, null, 4))
$('.data-banner').append('<br>');
getData.forEach((item, index) => {
//add requests to our queue:
allImageRequests.push(getImageAssets(item.sys.id));
});
listenForImages();
}
})
function getImageAssets(assetId) {
//I added a return here, so the XHR objects will be push()'d to the allImageRequests queue array:
return $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${assetId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (assetsData) {
const getAssetsData = assetsData.fields
$('.data-image').append(JSON.stringify(getAssetsData, null, 4))
$('.data-image').append('<br>');
}
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<pre class="data-banner">
<h4>Get Data Main Banner:</h4>
</pre>
<br>
<pre class="data-image">
<h4>Get Data for Each Image in Main Banner:</h4>
</pre>

Related

How to display the information from the input box

I am trying to learn how to work with angular and javascript more. Please let me know what I am doing wrong here.
When I input something into the text box, it should display
"hello {name} , would you like to play a game?
It displays the string without the input.
Also, when I run it, it says
object Object
.
(function (app) {
var JakesController = function ($scope, $http) {
$scope.JakesSampleModel = {name: ' '};
$scope.theSampleReturn = null;
var sendResponseData = function (response) {
if (response.data.error) {
console.log(data);
}
else {
$scope.theSampleReturn = response.data;
}
};
var sendResponseError = function (data) {
console.log(data);
}
$scope.senddata = function (params) {
return $http({
method: 'post',
url: '/home/servercall',
data: params
})
.then(sendResponseData)
.catch(sendResponseError);
};
};
app.controller("JakesController",['$scope', '$http', JakesController]);
}(angular.module("JakesFirstApp")));
Here is the HTML:
<div id="OutterDiv" ng-controller="JakesController" ng-app="JakesFirstApp">
<div id="JakesButton" class="button" ng-click="senddata()">Submit</div>
<input type="text" id="JakesTextBox" ng-model="theSampleReturn" />
{{theSampleReturn.result}}
Json result:
public JsonResult servercall(string name)
{
return Json(new { result = $"Hello {name}, Would you like to play a game?" }, JsonRequestBehavior.AllowGet);
}
In your html try to use {{theSampleReturn}} instead of {{theSampleReturn.result}} because you don't seem to have theSampleReturn.result set anywhere
If I understand you question correctly, then it looks like the solution is to update your template like so:
<input type="text" id="JakesTextBox" ng-model="JakesSampleModel.name" />
And then update your controller to correctly send the name to the server when senddata() is called:
$scope.senddata = function () {
// Construct params for post by getting data from your scope/model that's
// wired up to your input field
var params = { name : $scope.JakesSampleModel.name };
return $http({
method: 'post',
url: '/home/servercall',
data: params
})
.then(sendResponseData)
.catch(sendResponseError);
};

Submitting multiple forms with one button

I've looked at all the threads that already exist on this topic and have not been able to come up with a solution for my case.
I have multiple forms rendering with the help of Handlebars like this:
<ul>
{{#each listRecords}}
<form id="form{{id}}" action="/expand-list-records-save/{{../listId}}/{{id}}" method="POST">
<div class="record-box">
<li>{{recordTitle}} by {{artistName}} ({{releaseYear}})
<br>
<div>
<label>Paste media embed code here:</label>
<textarea class="form-control form-control-lg" name="embeddedmedia" cols="30" rows="10">{{embeddedMedia}}</textarea>
</div>
<br>
<br>
</li>
</div>
</div>
</form>
{{/each}}
</ul>
<input id="submit" class="btn btn-secondary btn-block" type="submit" value="Submit embed code" >
<script>
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
for (var i=0; i < document.forms.length; i++) {
console.log(`submitting ${document.forms[i].id}`)
document.forms[i].submit();
}
})
})
</script>
my Node.js + Express.js route looks like this
router.route('/expand-list-records-save/:listId/:recordId')
.post((req, res) => {
// console.log(req)
Record.findOne({
where: {
id: req.params.recordId
}
}).then(result => {
// console.log(req.body)
result.update({
embeddedMedia: req.body.embeddedmedia
})
}).then(() => {
console.log("sending list to view")
sendListDataToView({ params: {id: req.params.listId} }, res, 'view-list')
})
})
I'm having a few problems. First of all, this logic only executes a POST request for the item that the very last form on the page is for. Why is it that the console.log works for every single instance in my loop when iterating through all the document forms? From what I know, I think I need to use AJAX here somehow to execute all the POST requests. And the second main thing that I don't think is giving me problems at this point, but will once I get the first issue solved, is that my route is not written to handle a batch of requests like I need it to.
UPDATE
Upon a recommendation in comments, I decided try and write an Ajax request to post all of the forms to a separate route which will handle it from there. How do I pass an array of forms to the data parameter? I get the Uncaught RangeError: Maximum call stack size exceeded error this way:
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
$.ajax({
type: 'POST',
url: window.location.origin + $('h3')[0].innerText,
data: document.forms,
success: (data) => {
console.log(data)
}
})
})
})
After going through some other examples, I tried rewriting original submit script like this. And, in this case, it does not pick up the action attribute.
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
$('form').each(() => {
var that = $(this);
$.post(that.attr('action'), that.serialize())
})
})
})
So, I have finally come up with a solution, if anyone is interested. Perhaps not prettiest, but it works.
<script>
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
var counter = 0;
var totalForms = $('form').length;
$('form').each((i, form) => {
const redirectIfDone = () => {
if (counter === totalForms) {
alert("all forms updated successfully")
window.location.replace(window.location.origin + '/list/' + $('h3')[0].innerText)
}
}
if (!($(form).find('textarea').val())) {
counter++
redirectIfDone();
return true;
} else {
$.ajax({
type: 'POST',
url: $(form).attr('action'),
data: $(form).serialize(),
success: (data) => {
counter++;
redirectIfDone();
}
})
}
})
})
})
</script>
Virtually no changes to the route. Overall, I'm still interested in seeing other possible solutions.
router.route('/expand-list-records-save/:listId/:recordId')
.post((req, res) => {
Record.findOne({
where: {
id: req.params.recordId
}
}).then(result => {
result.update({
embeddedMedia: req.body.embeddedmedia
})
res.end()
})
})

Laravel + Vue.js. Load more data when i click on the button

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.

Get the article's view times using Vue.js and Laravel 5.3

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.

Deal with the result of postJSON

I'm trying to implement something with jQuery and Vue.js:
And here is my script part:
<script>
function initVM(result) {
// alert(result.num)
var vm2 = new Vue({
el: '#vm2',
data: {
// ③bind one item of the result as example
rrr: result.num
}
});
$('#vm2').show();
}
$(function() {
var vm = new Vue({
el: '#vm',
data: {
content: ''
},
methods: {
submit: function(event) {
event.preventDefault();
var
$form = $('#vm'),
content = this.content.trim();
// ①post textarea content to backend
$form.postJSON('/api/parse', {
content: content
}, function(err, result) {
if (err) {
$form.showFormError(err);
}
else {
// ②receive a result dictionary
$('#vm').hide();
initVM(result);
}
});
}
}
});
});
</script>
Here is my html part:
<html>
<form id="vm", v-on="submit: submit">
<textarea v-model="content" name="content"></textarea>
<button type="submit">Have a try!</button>
</form>
<div id="vm2" style="diplay:none;">
<!-- ④show the result-->
The result:
{{ rrr }}
</div>
</html>
Here is the definition of postJSON
<script>
// ...
postJSON: function (url, data, callback) {
if (arguments.length===2) {
callback = data;
data = {};
}
return this.each(function () {
var $form = $(this);
$form.showFormError();
$form.showFormLoading(true);
_httpJSON('POST', url, data, function (err, r) {
if (err) {
$form.showFormError(err);
$form.showFormLoading(false);
}
callback && callback(err, r);
});
});
// ...
// _httpJSON
function _httpJSON(method, url, data, callback) {
var opt = {
type: method,
dataType: 'json'
};
if (method==='GET') {
opt.url = url + '?' + data;
}
if (method==='POST') {
opt.url = url;
opt.data = JSON.stringify(data || {});
opt.contentType = 'application/json';
}
$.ajax(opt).done(function (r) {
if (r && r.error) {
return callback(r);
}
return callback(null, r);
}).fail(function (jqXHR, textStatus) {
return callback({'error': 'http_bad_response', 'data': '' + jqXHR.status, 'message': 'something wrong! (HTTP ' + jqXHR.status + ')'});
});
}
</script>
The process can be described as:
Post the content to backend
Receive the result and hide the form
Create a new Vue with the result
Show the result in a div which is binding with the new Vue instance
Actually, I do receive the result successfully, which is proved by the alert(result.num) statement, but nothing find in vm2's div except The result:
Where is the problem? Or please be free to teach me a simpler approach if there is, because I don't think what I am using is a good one.
Here's questioner.
Finally I found where is the problem.
The problem lays in Mustache: {{ }}
I use jinja2, a template engine for Python and Vue.js, a MVVM frontend framework. Both of them use {{ }} as delimiters.
So if anyone got trapped in the same situation with me, which I don't think there will be, please:
app.jinja_env.variable_start_string = '{{ '
app.jinja_env.variable_end_string = ' }}' # change jinjia2 config
OR
Vue.config.delimiters = ['${', '}'] # change vue config

Categories