Svelte App can read firebase database, but not add - javascript

Hello stackoverflow community.
I am making a simple database application using Svelte, Materialize CSS, and firebase.
I am able to read from the firebase database, so I know it is not a credentials problem, however upon using the "addLink" function below, the database does not update. The alerts show the date, and newLink string correctly as well. Code in context is below as well.
function addLink() {
alert(date.getTime());
db.collection("links").add({ link: newLink, id: date.getTime() });
}
<script>
import GetForm from "../layout/GetForm.svelte";
import TranslateButton from "../layout/TranslateButton.svelte";
import { db } from "../firebase.js";
let arrList = [];
let newLink = "";
let date = new Date();
db.collection("links")
.orderBy("id", "asc")
.onSnapshot(snapData => {
arrList = snapData.docs;
});
function addLink() {
alert(date.getTime());
db.collection("links").add({ link: newLink, id: date.getTime() });
}
</script>
<div class="container right-align" style="margin-top: 30px;">
<TranslateButton />
</div>
<div class="container" style="margin-top: 150px;">
<div class="row">
<div class="center-align">
<div class="row">
<form class="col s12">
<div class="row">
<div class="input-field col s10">
<textarea
id="textarea1"
class="materialize-textarea"
bind:value={newLink} />
<label for="textarea1">Put your URL here.</label>
</div>
<button
class="btn waves-effect waves-light col s2"
on:click={addLink}>
Send
<i class="material-icons right">arrow_forward</i>
</button>
</div>
</form>
</div>
</div>
</div>
<div class="row">
<div class="center-align">
<GetForm />
</div>
</div>
<ul>
{#each arrList as listItem}
<li>{listItem.data().link}</li>
{/each}
</ul>
</div>

As per the official documentation Set a Document indicates, you need to use the method set() to add documents to your collection. One example of an insert in the database is the following - the documentation is in Node.js, but I believe it should help you as a starting point.
let data = {
link: 'Link you want',
id: 'Id you want'
};
let setDoc = db.collection('link').doc('Id').set(data);
This is just a simple example of adding a document to a collection, but it should help you understading the logic for your.
Besides that, in the below articles, you should get more information on how to perform queries on Firestore.
Get started with Cloud Firestore
Adding Data
Let me know if the information helped you!

Adding a new entry in Firestore DB
Which tutorial are you following for firebase? That's not how you add data to firebase. Replace add() with set()
For Automatically generated ID
Pass your nothing as an argument to the doc()
// For automatically generated id
db.collection('links')
.doc()
.set({ link: newLink })
.then(() => { // fetch the doc again and show its data
ref.get().then(doc => {
console.log(doc.data()) // prints {link: newLink, id: 1321415}
})
})
For using your own id
Pass your id as an argument to the doc()
// Or you could set your own id, which is in your case,
// Pass your `id` as argument for `doc()`
db.collection("links")
.doc(date.getTime())
.set({
link: newLink
})
Add data to Cloud Firestore

Related

TypeError: u.split is not a function

I am working on a whatsapp clone app using react, firebase
messages, user name, timestamp are recieved from firestore using this code:
<div className="chat__body">
{messages.map(message => (
<p className={`chat__message ${true && "chat__reciever"}`}>
<span className="chat__name">{message.name}</span>
{message.message}
<span className="chat__timestamp">
{new Date(message.timestamp?.toDate())
.toUTCString()}
</span>
</p>
))}
</div>
<div className="chat__footer">
<InsertEmoticonIcon />
<form >
<input value={input} onChange={(event) =>
setInput(event.target.value)} placeholder="Type a message" type="text" />
<button onClick={sendMessage} type="submit">Send </button>
</form>
<MicIcon />
</div>
The input field texts are captured in {sendMessage} which are viewed in console as input value.
//probably sendMessage onClick event is causing this error.
const sendMessage = (event) => {
event.preventDefault();
console.log("You typed ", input);
db.collection('groups').doc(groupId)
.collection(messages).add({
message: input,
name: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
setInput('');
}
as the firestore is set, the inputs are set as messages, name and timestamps set from google authentication in collection like this:
When I type on the input field, those inputs are supposed to be showed in chat instead showing error:TypeError: u.split is not a function
You're passing messages as an array, not as a string.
Change your code to
db.collection("groups").doc(groupId).collection("messages").add({
message: input,
name: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
Looking at the error stack the first project file seems to be:
...
at e.collection (...)
at sendMessage (Chat.js:47)
...
Since you supplied the sendMessage definition I will assume that the definition is located in Chat.js. You haven't given us any line numbers to work with, however the line above "at sendMessage" tells us what function/method you called in sendMessage that caused the error. In your case this was a collection call.
Looking at your code:
const sendMessage = (event) => {
event.preventDefault();
console.log("You typed ", input);
db.collection('groups').doc(groupId)
.collection(messages).add({
message: input,
name: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
setInput('');
}
You make 2 collection calls. .collection('groups') and .collection(messages). Assuming that db is an Firestore instance the first collection call has the following signature:
collection ( collectionPath : string ) : CollectionReference < DocumentData >
The second collection call is called upon a DocumentReference but has the same signature.
Your first collection call provides a string 'groups' as argument which looks fine. Your second collection call provides messages as the argument, which is probably an array (you call messages.map in your first code block). Since the method is expecting a string it throws an error.

What is the best way to create an populate complex objects with data from api?

I am trying to build a simple todo app. I have a server running on flask that returns a page when main route is accessed and has different endpoints like add_task, delete_task etc. and I access these endpoint via fetch() function and return an updated list of task from them in JSON.
My question is - What is the best way to create and populate a task list structure with following example markdown (below) with received info?
Markdown example:
<div class="task-list-container">
<div class="task-container">
<div class="task-header">
<span>completion progress...</span>
<span>created at ...</span>
<span>due date...</span>
</div>
<div class="task-description">
description...
</div>
<div class='task-comments'>task comments...</div>
<div class='task-tags'>tags...</div>
</div>
...another task
</div>
Returned JSON structure:
[
{
"task-id": int,
"completion": bool,
"created": date,
"due date": date,
"description": str,
"comments": [str],
"tags": [str]
},
{another task}
]
I could easily do it with React by creating component for it and simply populate a list of them by mapping through returned data but is there a way to do it neatly with vanilla JS?
There are several ways to do it. One is to use a template literal to inject the variable task fields. For the asynchronous part (reading from the API), you would use async and await.
Here is how it could work for a dummy JSON provider:
function renderTask(task) {
return `<div class="task-container">
<div class="task-header">
<div>Task id: ${task.id}</div>
<div>Task is ${task.completed ? "completed" : "in progress"}</div>
</div>
<div class="task-description">
${task.title}
</div>
</div>`;
}
async function refresh() {
let response = await fetch("https://jsonplaceholder.typicode.com/todos/");
let arr = await response.json();
let html = arr.map(renderTask).join("\n");
document.querySelector(".task-list-container").innerHTML = html;
}
refresh();
.task-container { border: 1px solid; margin-top: 5px }
.task-header { font-weight: bold }
<div class="task-list-container">
</div>
You can loop through the tasks and for each task create the elements to the DOM,
Something like the below:
Object.keys(data).forEach((item, i) => {
let taskItem = document.createElement('div');
let taskItemHeader = document.createElement('div');
let taskItemDescription = document.createElement('div');
....
taskItemDescription.innerHTML = item.description;
taskItemHeader.appendChild(taskItemDescription);
taskItem.appendChild(taskItemHeader);
....
});

Issues Building Datatable Within ng-bootstrap Modal

I have an Angular 7.2.15 project that I have installed ng-bootstrap (https://ng-bootstrap.github.io/#/components/modal/examples) on with great success until now when I realize I need to change the contents of the modal dialog.
The dialog will simply display the result of an API call which I know is coming back fine from Chrome Network views, essentially for each record that comes back, we need to display the "name" attribute in the datatable as a link (eventually, that link will load a saved query by its name) and an icon to delete it.
Here are the two important snippets from the search-bar.component.html in question:
<ng-template #contentOpen let-modal>
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Open Query</h4>
<button type="button" class="close" aria-label="Close" (click)="modal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Select from the following list of saved queries:
<table #dataTable class="table">
<thead>
<tr class="table-header">
<td class="max-width-80">Search Name</td>
<td class="max-width-20">Delete</td>
</thead>
<tbody class="table-body">
</tbody>
</table>
</div>
</ng-template>
...
<div class="col-md-1">
<button class="SearchGrayButton" (click)="openModal(contentOpen)">Open Search <span class="fa fa-folder-open-o"></span></button>
</div>
Then, in our search-bar.component.ts - we successfully get the SearchHistory from our webservice from the filteringService, but rendering the results poses the issue. If I take the datatable out from the ng-template #contentOpen section above, it will render the datatable fine (albeit not in the modal as we would like it to be). So within the modal, it does not render at all, but outside of it, the table will render without issue.
openModal(content) {
this.GetSearchHistory();
this.modalService.open(content, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed`;
});
}
/* Obtains all of the search history for a user and renders table in
the modal to display them */
GetSearchHistory() {
this.filteringService.getSearchHistory().subscribe(
resp => {
console.log(resp, 'res');
let r: WebServiceResponse;
r = resp as WebServiceResponse;
this.searchHistory = r.data;
this.buildTableForSearchHistory();
},
error => { console.log(error, 'error'); }
);
}
buildTableForSearchHistory() {
const options = {
sDom: 't',
renderer: 'bootstrap',
destroy: true,
data: this.searchHistory,
columns: [
{ data: 'name',
className: 'dt-center max-width-10'
}
],
order: [0, 'desc'],
createdRow( row, data, dataIndex ) {
},
drawCallback: () => {
}
};
this.dataTable = $(this.table.nativeElement);
this.dataTable.DataTable(options);
}
As a test, I also set up a mock "refresh" button of sorts within the modal that would trigger the getSearchHistory() we see above and build the table after we know the modal is in focus, and this also does not resolve the issue. The console complains about the following line, which makes sense as I think it's having trouble finding the table in question to render to:
this.dataTable = $(this.table.nativeElement);
I don't know if it's needed beyond context especially for how simple it is, but a sample of the web service's JSON response in question:
{"status":null,"code":null,"messages":[],"data":[{"id":1,"userId":null,"name":"manual test name","queryText":null,"filtersJson":null},{"id":2,"userId":null,"name":"testname","queryText":null,"filtersJson":null},{"id":3,"userId":null,"name":"testname","queryText":null,"filtersJson":null}]}
Also noteworthy is we don't necessarily have to use DataTables here, as requirements really only are to display all the names as links and perhaps have the queryText and filtersJson as metadata since we'll need them for later. I just thought it might be a "nice to have" if we allow them to sort the results.
Does anyone have any thoughts or ways to help resolve this?
Realized binding in this manner resolves it since there was no need for the DataTables usage:
<div class="modal-body">
Select from the following list of saved queries:
<ul>
<li *ngFor="let s of searchHistory">{{s.name}}</li>
</ul>
</div>

Using $set to grab jquery variable and push it to an vue array object

I'm pulling data from a API and using jQuery's getJson method to extract the data I'm then trying to assign the data to a vue array object by utilizing app.$set.
So far I've been able to extract the data and assign it to the vue array but I can only access one thing at a time.
<div id="app">
<div v-once id="movies">
{{movieCall()}}
</div>
<div v-for="(movie,index) of movies" class="card" style="width: 18rem;">
<!-- <img src="..." class="card-img-top" alt="..."> -->
<div class="card-body">
<div class="card-title">{{movie}}</div>
</div>
</div>
</div>
var app = new Vue({
el: "#app",
movies: [
],
},
methods:
$.getJSON("https://api.themoviedb.org/3/movie/now_playing?api_key=9d9f46b8451885697e5cf7d1927da80f", function (movie) {
for (let i = 0; i < 3; i++) {
app.$set(app.movies, i, movie.results[i].title);
}
for (var x = 0; x < app.movies.length; x++) {
console.log(app.movies[x])
}
})
},
I'm extracting the movie and setting the title to the movie array but I'm wanting to assign it instead to a movie{title} object. This is so when I go through my v-for loop I can refer to the movie object array as movie.title, movie.overview, etc. to print them all. e.g.
In other words, is there a way to do:
app.$set(app.movies.title, i, movie.results[i].title);
app.$set(app.movies.overview, i, movie.results[i].description);
etc.
and have my movie array set up as:
movie[
{title:}
{description:}
]
and finally loop through like:
<div v-for(movie, index) of movies>
<div class="titles">
{{movie.title}}
</div>
<div class="descriptions">
{{movie.description}}
</div>
</div>
If you want to access movies like:
<div v-for="(movie, index) of movies">
...
{{movie.title}}
...
{{movie.description}}
Then populate it as:
app.$set(app.movies, i, {title: movie.results[i].title, description: movie.results[i].description});
Or, if i is incrementing one by one, the equivalent:
app.movies.push({title: movie.results[i].title, overview: movie.results[i].description});
Your code needs a bit of an upgrade / correction before it's OK, so I prepared a snippet for you that does the same thing (with a mockup JSON response), so you can see that you don't need app or $set for this.
var app = new Vue({
el: "#app",
data: {
movies: []
},
methods: {
movieCall() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => {
json.forEach(movie => this.movies.push(movie))
// or just simply:
// this.movies = json
})
}
},
created() {
this.movieCall()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(movie,index) in movies">
<div>
<div>{{index + ". Title: " + movie.title}}</div>
<div>{{index + ". Body: " + movie.body}}</div>
</div>
</div>
</div>
I tried to keep this as close to your code as possible (but CSS classes were taken out).
Basically Vue is reactive (data property), so if you update the data property movies the template should immediately react to that and update the UI. That's the core idea of a JS frontend framework.
Accessing data in object syntax (like movies.title, with a dot), is another matter. In your project you set up a data property - movies, that's an array. An array can hold any type of elements - objects too.
The idea is that you download the objects and then read them (one by one) into the movies data property. Or, if you receive an array, then this.movies = response (make them equal, assigning the response to the movies array.
Then there's an other thing: Vue templates have their lifecycle, and created() is a hook, that can be used to execute functions when the template is created. If you want something to run once, you should utilize these lifecycle hooks. (Of course, if your app reloads this template, then it executes the hook many times. To avoid this (downloading something multiple times) you should look into state management, but that's a large topic in itself.)
Add data key to hold the data set
<script>
var app = new Vue({
el: "#app",
data: {
movies: [],
},
methods:
$.getJSON("https://api.themoviedb.org/3/movie/now_playing?api_key=9d9f46b8451885697e5cf7d1927da80f", function (movies) {
for (var x = 0; x < movies.results.length; x++) {
//console.log("\nTitle"+movies.results[x].title);
//app.movies.$set(x, movie.results[x].title);
app.movies.push(movies.results[x].title);
console.log(JSON.stringify(app.movies))
}
})
});
</script>
And try with this command
app.movies.push(movie.results[i].title);
Here is a working example or sample which i created : https://plnkr.co/edit/EnepqQqXzEquJlxqjzn6?p=preview
Ref1: https://v2.vuejs.org/v2/guide/list.html

ember js form submit to view

With emberjs (1.0.0rc1) and ember-data (very recent build #36d3f1b), I am trying to setup a basic crud example. I can't figure out how to retrieve a submitted model from a view and then update/save it. Here is what my code looks like:
App = Ember.Application.create();
App.Router.map(function() {
this.resource('posts', function() {
this.route('create');
this.route('edit', {
path: '/:post_id'
});
});
});
App.PostsIndexRoute = Ember.Route.extend({
model: function() {
return App.Post.find();
}
});
App.PostsCreateView = Ember.View.extend({
submit: function () {
console.log(this.get('model')); // undefined
}
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string')
});
App.Post.FIXTURES = [{
id: 2,
title: 'a',
body: 'aa'
}, {
id: 5,
title: 'b',
body: 'bb'
}];
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.FixtureAdapter.create({
simulateRemoteResponse: false
})
});
and the create template:
<script type="text/x-handlebars" data-template-name="posts/create">
{{#view App.PostsCreateView tagName="form" classNames="form-horizontal"}}
<h3>Create</h3>
<div class="control-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input type="text" id="title" placeholder="Title" />
{{view Ember.TextField valueBinding="title"}}
</div>
</div>
<div class="control-group">
<label class="control-label" for="body">Body</label>
<div class="controls">
<input type="password" id="body" placeholder="Body" />
</div>
</div>
<div class="control-group">
<div class="controls">
<button class="btn">Create</button>
</div>
</div>
<div>{{#linkTo 'posts'}}Back{{/linkTo}}</div>
{{/view}}
</script>
How can I access the value of the form (serialized to the model) from the submit hook? Secondly, how do I then persist this via the FixtureAdapter?
The first part of your question is tricky to answer because it's actually pretty simple, but in order for it to be simple you'll need to change the way you think about model CRUD. Your "submit" function is not needed. When you instantiate a view) it should have an instance of your model bound to it. (If you're creating a new one it will be a new, empty instance.) When you make changes to that model in the view, they are made instantly; no need for submit. (After all, what would you submit to?)
I'm not sure this actually answers your question, but maybe it puts you on a track to answering it.
I can be a lot more definite about your second question, persisting a value via the FixturesAdapter: you can't. The FixturesAdapter is just that, an adapter for loading fixtures (essentially read-only data) into the store. Changes made to models from the FixturesAdapter will only last until the app is reloaded. To persist data you will need to transition from the FixturesAdapter to a different adapter (probably the RestAdapter).
this article deals with building a small example app including a creation form (it's originally in portuguese, but fortunately, google translate doesn't garble the text too much in this case).
the code can be found here (the important part is way down the page) and a live example here.

Categories

Resources