I am working on a To-Do list app and would like to save data on the local storage of the device, but I can't figure out what it is I need to do.
In my app.js file I have the following code which allows me to add new tasks and to edit them:
.controller('DashCtrl', function($scope, $ionicPopup, $ionicListDelegate) {
$scope.tasks =
[
{title:"Example 1", completed: true},
{title:"Example 2", completed: false},
];
$scope.newTask = function(){
$ionicPopup.prompt({
title:"New Task",
template: "Enter task:",
inputPlaceholder:"Please add your task",
okText:'Create task'
}).then(function(res){
if (res) $scope.tasks.push({title: res, completed: false});
})
};
$scope.edit = function(task){
$scope.data = { response: task.title};
$ionicPopup.prompt({
title: "Edit Task",
scope: $scope
}).then(function(res){
if(res !== undefined) task.title = $scope.data.response;
$ionicListDelegate.closeOptionButtons()
})
};
The data is stored in the scope so I tried the following:
localStorage.setItem(scope));
And my idea is to call this function every time a new task is added, so that the storage is always up to date. But this does not seem to be working, what am I doing wrong?
This is the way to go:
https://github.com/gsklee/ngStorage
"An AngularJS module that makes Web Storage working in the Angular Way. Contains two services: $localStorage and $sessionStorage."
localStoragestores strings. If your data is already a string, a simple
localStorage.setItem($scope.data) should work. If it's a valid JSON, you stringify it and store as before.
localStorage.setItem(JSON.stringify($scope.data))
#Ladmerc would this work?
$scope.newTask = function(){
$ionicPopup.prompt({
title:"New Task",
template: "Enter task:",
inputPlaceholder:"Please add your task",
okText:'Create task'
}).then(function(res){
if (res) $scope.tasks.push({title: res, completed: false});
localStorage.setItem(JSON.stringify($scope.data));
localStorage.setItem($scope.data);
})
};
I think there are multiple issues with your approach, first being that you are trying to save the whole $scope object, you can place a console.log() to see how much data it contains, spoiler alert, a lot. The $scope object can be huge and there is no need to save it, what you want is to save the tasks. As mentioned by others, you also need to stringify your data, because localStorage only accepts strings. Luckily you don't have to solve these issues yourself, I would highly recommend you take a look at localForage, it is a library that:
localForage is a fast and simple storage library for JavaScript.
localForage improves the offline experience of your web app by using
asynchronous storage (IndexedDB or WebSQL) with a simple,
localStorage-like API.
You can find more information about localForage in the documentation and there is also an Angular wrapper available.
In your case, this would be an example of how to use this library:
$localForage.setItem('tasks', $scope.data).then(function() {
console.log("The tasks have been saved!");
});
Related
I'm beginner with angularJs and have, as i think, simple problem with whatching changes in my $scope.
I have an object $scope.tasks which i received from server. It looks like:
{
2: {id: 2, name: 'name1', user: 3},
3: {id: 3, name: 'name2', user: 1},
4: {id: 4, name: 'name3', user: 4},
}
also i have a function which update user in task:
$scope.managePlannedIterationTask = function(taskId, userId) {
var data = {
'taskId' : taskId,
'userId' : userId
};
$http.post("url", data).success(function(data, status) {
$scope.tasks[taskId].user = userId; //#1
});
};
also i have custom function $scope.test();
this function must to be executed after every change of user in tasks, for example after update query and updating at //#1
I tried to use for this goal $watch and $watchCollection:
$scope.$watchCollection('tasks', function () {
$scope.test();
})
But this doesn't help. And now i must to insert $scope.test() in all places when i'm update my $scope.tasks. It's very sad.
How can i do it automatically?
Using watch isn't be good practice to have it in code. I'd say that have an call to test method when you update call gets succeeded.
And inside a success callback of post call do make an another call to get updated tasks list to make your view data properly in sync with server data. Currently you are updating data on client side itself for particular record, that doesn't make sense in data-centric application which is widely used by multiple users. If suppose you have 1000 users which are accessing this application, and while you are updating any record, other users also has updated/added any record on the same page. So that will add inconsistent behavivor inside application. So that's why I'm suggesting you to make one more ajax again to fetch data & to keep your application to look more consistent and accurate. Making an one more ajax call wouldn't take 100ms, which is less cost paid for good reason.
Code
$scope.managePlannedIterationTask = function(taskId, userId) {
var data = {
'taskId' : taskId,
'userId' : userId
};
$http.post("url", data).then(function(response) {
var data = response.data;
$scope.getTasks();
$scope.test();
});
};
$scope.getTasks = function(){
$http.get('getTasksUrl').then(function(response){
$scope.tasks = response.data;
});
}
You can write a directive with watcher and put on models which you want to be watched. But if I understood you correctly you need to update data on your page after successful post request, you can achieve this by firing get request, after successful post, or at worse case scenario you can use route or state reload depending on which router you use.
I'm trying to add some basic CRUD functionality to MEAN stack. I've created a RESTful service that works and I'm confused about how to wire it all up. I can get it to work, but I want to make sure I'm doing things the best way and not creating an unnecessary hack.
My api route for a single Person is like this:
// Find one person
app.get('/api/person/:id', function(req, res) {
Person.find ( {_id: req.params.id },
function(err, data){
res.json(data);
}
)});
// Find group of people
app.get('/api/person', function(req, res) {
// use mongoose to get all people in the database
Person.find(function(err, data) {
res.json(data);
});
This seems to work in that if I go to a URI with an ID, as in localhost://3000/api/person/23423434, I see JSON data like this:
[
{
"_id": "532d8a97e443e72ef3cb3e60",
"firstname": "Horace",
"lastname": "Smith",
"age": 33
}
]
This tells me the basic mechanics of my RESTful api are working. Now I'd like to display that data with angular in a template like so:
<h3>{{ person.firstname + ' ' + person.lastname }} </h3>
To do that, I just need to create a $scope.person object with get() or query(). Here's the relevant part of my app:
angular.module('crudApp', ['ngRoute', 'ngResource'])
.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/api/person/:id',
{
templateUrl: 'partials/person.html',
controller: 'PersonCtrl'
});
}])
.factory('Person', function($resource){
return $resource('api/person/:id', { id: '#_id'});
})
.controller('PersonCtrl', function($scope, $routeParams, Person){
$scope.person = Person.get( { id: $routeParams.id } ); // Having trouble here!
});
The trouble I'm having is that get() fails with an error (Error: [$resource:badcfg]). On the other hand, if I use Person.query(), I get back an array, which means I need to change my template to the following:
<h3>{{ person[0].firstname + ' ' + person[0].lastname }} </h3>
This works, but seems strange and isn't like what I've seen in angular tutorials. The only other solution I've found is to set $scope.person in a callback:
Person.query({ id: $routeParams.id }, function(person){
$scope.person = person[0];
});
This works with my original unmodified template. Is it the best or right way to work with RESTful apis like this? Is there a better way?
Answer: the answer is in comment below. My problem is that api is using Person.find() but should be using Person.findOne( { _id: req.params.id }); Using findOne() returns a single object.
Your api should look like this:
route -> '/api/person/:id'
return single person
route -> '/api/person'
return array of persons
then if you want to get by id, you shall use get method, or if you want to get all persons, you should use query method. Your mistake is that you shall return single object when getting by id
This is a two stage problem when working with backbone.js and a web api controller.
I have a simple web api controller that returns a JSON string, in fiddler the result looks like this:
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading":"The heading"
}
I use the following code to fetch a user from my web api
var user = new Usermodel({ id: "1" });
user.fetch({
success: function (u) {
console.log(u.toJSON());
}
});
now my backbone user object looks like this
{
id: "1",
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading": "The heading"
}
}
When I try to bind this backbone model object to my view template that looks like this
<form>
<input type="text" value="<%=Heading%>" />
<input type="submit" value="Save" />
</form>
i get, Heading is undefined but when I use id it binds just fine? It seems like underscore does not like the backbone model object and just want a plain JSON object just like the one I get from my web api?
The second problem with this is that when I save my model with user.save({ Heading: "my new heading }); the payload to my web api is the backbone model which is completely wrong because my api expects a user object like this to be sent to the server:
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading":"The heading"
}
and not the backbone model with the real object wrapped inside. Is it possible to solve so that underscore can handle backbone models and tell backbone to only send the payload that my end point expects?
You may be able to solve the problem by following these steps:
In addition to using fiddler to inspect your response, look at the response on the network tab of Chrome Developer Tools. If the response does not look like this, then your web api is not returning a valid json response, the problem is most likely within your web api. You need to get/provide more information about your web api to solve the problem. Verify that the response looks like this:
After verifying that the response from the web api is correct, check out the following jsfiddle I modified:
http://jsfiddle.net/J83aU/23/
Fix your client side code referencing the example I have provided.
Properly instantiate the Backbone objects.
Call the view.render function at the correct step, after the response is received from the server.
Make sure that the main content div is actually rendered before creating a view which depends on it for the 'view.el' property.
Declare the 'view.el' property properly, with a string rather than jQuery object.
Use development Backbone and underscore to enable debugging, an important concept when learning to use open source frameworks such as Backbone.
Use jsfiddle's echo/json api to mock a valid ajax json response, exactly as described in step 1.
The following json example you submitted is not even valid json, if you update your question with valid json example, it would be easier to solve the problem. It is unlikely that Backbone created this non-json structure and more likely that you have submitted it here incorrectly.
{
id: "1",
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading": "The heading"
}
}
Finally, try to provide a screenshot of the http headers or something for the problem that is occurring when you call model.save().
Read over the Backbone documentation for model.save() and make sure you are doing everything just as the example provided.
You may be able to workaround Backbone's funky save function by forcing your attributes into POST parameters using ajax options:
$.fn.serializeObject = function(){
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
var saveView = Backbone.View.extend({
events:{
'click #saveSubmitButton':'submit'
},
submit:function (event) {
event.preventDefault();
var view = this,
attributes = $('#saveForm').serializeObject();
this.model.save(attributes, {
data:attributes,
processData:true,
success:function (model) {
//....
}
});
},
render:function () {
//.......
}
});
The attributes property of your model should be unaltered. Send those to your template call:
var MyModel = Backbone.Model.extend();
var newModel = new MyModel({
"$type": "MvcApplication.Models.Article, MvcApplication",
"Heading":"The heading"
});
var html = _.template(templateVar, newModel.attributes);
In your templateVar, which is your templated markup, you should be able to reference $type and Heading directly.
If you have a look at the jsFiddle through a debugger like Firebug you can see that the way you construct the model's URL is not working out, because the forward slash gets encoded. Can you try to modify your model declaration to this:
var Usermodel = Backbone.Model.extend({
url: function () {
return '/api/page/articles/' + this.get('id');
}
});
var user = new Usermodel({
id: '85'
});
And see if you still get the same JSON. Basically if you don't have a Backbone.sync override you are using built-in retrieval that for one shouldn't produce invalid JSON.
demo fiddle (with problem) http://jsfiddle.net/mjmitche/UJ4HN/19/
I have a collection defined like this
var Friends = Backbone.Collection.extend({
model: Friend,
localStorage: new Backbone.LocalStorage("friends-list")
});
As far as I'm aware, that's all I need to do to get local storage to work (in addition to including it below backbone.js)
One thing I wasn't sure about, does the name "friends-list" have to correspond to a DOM element? I'm trying to save the "friends-list" so I called it that in local storage, however, localstorage doesn't seem to require passing a class or an id.
Here's a fiddle where you can see what I'm trying to do http://jsfiddle.net/mjmitche/UJ4HN/19/
On my local site, I'm adding a couple friends, refreshing the page, but the friends are not re-appearing.
Update
I've also done the following in my code on my local site
console.log(Backbone.LocalStorage);
and it's not throwing an error.
My attempt to debug
I tried this code (taken from another SO answer) in the window.AppView but nothing came up in the console.
this.collection.fetch({}, {
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
})
From the fine manual:
Quite simply a localStorage adapter for Backbone. It's a drop-in replacement for Backbone.Sync() to handle saving to a localStorage database.
This LocalStorage plugin is just a replacement for Backbone.Sync so you still have to save your models and fetch your collections.
Since you're not saving anything, you never put anything into your LocalStorage database. You need to save your models:
showPrompt: function() {
var friend_name = prompt("Who is your friend?");
var friend_model = new Friend({
name: friend_name
});
//Add a new friend model to our friend collection
this.collection.add(friend_model);
friend_model.save(); // <------------- This is sort of important.
},
You might want to use the success and error callbacks on that friend_model.save() too.
Since you're not fetching anything, you don't initialize your collection with whatever is in your LocalStorage database. You need to call fetch on your collection and you probably want to bind render to its "reset" event:
initialize: function() {
_.bindAll(this, 'render', 'showPrompt');
this.collection = new Friends();
this.collection.bind('add', this.render);
this.collection.bind('reset', this.render);
this.collection.fetch();
},
You'll also need to update your render to be able to render the whole collection:
render: function() {
var $list = this.$('#friends-list');
$list.empty();
this.collection.each(function(m) {
var newFriend = new FriendView({ model: m });
$list.append(newFriend.render().el);
});
$list.sortable();
return this;
}
You could make this better by moving the "add one model's view" logic to a separate method and bind that method to the collection's "add" event.
And a stripped down and fixed up version of your fiddle: http://jsfiddle.net/ambiguous/haE9K/
I need to query the database using a backbone collection. I have no idea how to do this. I assume that I need to set a url somewhere, but I don't know where that is. I apologize that this must be a very basic question, but I took a backbone course on CodeSchool.com and I still don't know where to begin.
This is the code that I have for the collection:
var NewCollection = Backbone.Collection.extend({
//INITIALIZE
initialize: function(){
_.bindAll(this);
// Bind global events
global_event_hub.bind('refresh_collection', this.on_request_refresh_collection);
}
// On refresh collection event
on_request_refresh_collection: function(query_args){
// This is where I am lost. I do not know how to take the "query_args"
// and use them to query the server and refresh the collection <------
}
})
The simple answer is you would define a URL property or function to your Backbone.Collection like so:
initialize: function() {
// Code
},
on_request_refresh_collection: function() {
// Code
},
url: 'myURL/whateverItIs'
OR
url: function() {
return 'moreComplex/' + whateverID + '/orWhatever/' + youWant;
}
After your URL function is defined all you would have to do is run a fetch() on that collection instance and it will use whatever you set your URL to.
EDIT ------- Making Collection Queries
So once you set the URL you can easily make queries using the native fetch() method.
fetch() takes an option called data:{} where you can send to the server your query arguments like so:
userCollection.fetch({
data: {
queryTerms: arrayOfTerms[], // Or whatever you want to send
page: userCollection.page, // Pagination data
length: userCollection.length // How many per page data
// The above are all just examples. You can make up your own data.properties
},
success: function() {
},
error: function() {
}
});
Then on your sever end you'd just want to make sure to get the parameters of your request and voila.