Question

I have a little AngularJS tool that inserts cards. My goal is to store them locally. I worked that out for the cards array, but not for the card content, that is "contenteditable" Can u help me with this and give me some best practice solutions?

Here's a Plunker (in JS) for that (the red big button deletes the localStorage. Be sure to have a wide window open): http://plnkr.co/edit/SlbWZ5Bh62MDKWViUsMr

This is my code (with CoffeeScript. For JS see the Plunker above):

This is the markup for the user input

<div class="card card-{{ card.color }}">
    <header>
        <p class="points" contenteditable></p>
        <p class="number" contenteditable>#</p>
        <h2 class="customerName"  contenteditable>{{ card.customer.name }}</h2>
        <h3 class="projectName" contenteditable>Project Name</h3>
    </header>
    <article>
        <h1 class="task" contenteditable>Title</h1>
        <p class="story" contenteditable>Description</p>
    </article>
    <footer>
        <div class="divisions">
        <p class="division"></p>
        <button ng-click="deleteCard()" class="delete">X</button>
        </div>
    </footer>
</div>
<div class="card card-{{ card.color }} backside">
    <article>
        <h2 class="requirement">Requirements</h2>
        <p contenteditable></p>
    </article>
</div>

Here you see my localStorage setup for the cards array that was in my controller above:

Card = (@color, @customer) ->

        $scope.cards = []

        json = localStorage.getItem "cards"
        getCards = JSON.parse(json) if json?
        $scope.cards = $scope.cards.concat getCards if getCards?

        $scope.reset = ->
            localStorage.clear()

        $scope.save = ->
            cards = []
            for card in $scope.cards
                cards.push
                    color: card.color
                    customer:
                        name: card.customer.name

            localStorage.setItem "cards", JSON.stringify(cards)

        $scope.addCardRed = (customer) ->
            $scope.cards.push new Card("red", customer)
            $scope.save()

How do I store the user inputs in the different fields in localStorage? I heards something about serialization, but I don't know what it means in my case!

Thank you so much in advance!

Was it helpful?

Solution

You can use the ng-model directive with any contenteditable field, just like you would do with an input or textarea. so, instead of trying to use the {{...}} braces to bind your model to your view, you should just use ng-model, and then just treat your editable DOM elements as if they were fields in a form.

For example, in your view :

<header ng-repeat="card in cards">
    <!-- These are just sample fields for the card object, but you can modify them -->
    <p class="points" contenteditable ng-model="card.points"></p>
    <p class="number" contenteditable ng-model="card.number"></p>
    <h2 class="customerName"  contenteditable ng-model="card.customer.name"></h2>
    <h3 class="projectName" contenteditable ng-model="card.projectName"></h3>
</header>

And then in your controller you would attach the model to the $scope as you've already done using $scope.cards = $scope.cards.concat getCards if getCards?. This will two-way bind your cards model to your controller's scope.

And then in your controller, to mirror the model data in LocalStorage, you can do it yourself using something like this:

In your controller:

....
//  for example's sake
$scope.cards = [  // the cards array would look something like this
    {points: 0, number: 5, customer: {name: 'bob'}, projectName: 'myProj1'},
    {points: 1, number: 6, customer: {name: 'joe'}, projectName: 'myProj2'},
    {points: 2, number: 7, customer: {name: 'bill'}, projectName: 'myProj3'},
    {points: 3, number: 8, customer: {name: 'jerry'}, projectName: 'myProj4'}
];
....
// listen to all changes to the $scope.cards array
$scope.$watch('cards', function(newVal){
    var str = angular.toJson(newVal); // serialize the cards array into a string
    // NOTE: the 'someKey' string is the key that you'll use later to retrieve it back
    window.localStorage['someKey'] = str;  // store the serialized string in localStorage
}, true);
....

In the above example, the angular.toJson(newVal) will take the newVal variable (which is just a reference to the "recently updated" cards array), and will serialize it into JSON string (ie angular.toJson just basically wraps the native JSON.stringify() method). In order to put a javascript object into LocalStorage it must be serialized to a string because you can only put primitives as the value in a LocalStorage key.

So the newVal will get put into localStorage looking something like this:

"[{"points":0,"number":5,"customer":{"name":"bob"},"projectName":"myProj1"},{"points":1,"number":6,"customer":{"name":"joe"},"projectName":"myProj2"},{"points":2,"number":7,"customer":{"name":"bill"},"projectName":"myProj3"},{"points":3,"number":8,"customer":{"name":"jerry"},"projectName":"myProj4"}]"

And then later (whenever you need it) you can retrieve the cards array from localStorage again using the following:

var str = window.localStorage['someKey'];
$scope.cards = angular.fromJson(str);

Or you can use a library to do the serialization/saving like this one: https://github.com/grevory/angular-local-storage. I've never used it but it does exactly what you want it to.

Hopefully that helps clarify things a bit.

UPDATE: This is beyond the scope of this question, but since you asked. It sounds like your'e not grasping the concept of the ng-repeat and ng-model directives. These are probably the two most famous (and most widely used) directives in Angular. By combining these two directives (as in the view example above), it will automatically keep your model (ie $scope.cards) and your view (ie <header>) in sync as users edit the data in your view. ng-repeat will "automagically" create a new header element for every card in your cards array (hence the ng-repeat="card in cards"). So as new cards get added or cards are removed from the cards array, Angular will add or remove <header> elements as needed. Then, using the contenteditable and ng-model directives, Angular will bind the content of those editable DOM elements to the values for each card. Typically ng-model is used with form elements (ie inputs/textareas/selects) but it can also be used for any editable field. Think of your editable elements as if they were an input, and then take a good long look at the <input ng-model="" /> example from the angular docs found here. That should help.

OTHER TIPS

Now I know what confused me!

The Two-Way-Binding of my card properties didn't work with contenteditable. I removed each contenteditable attribute and instead of p, h2 and so on I replaced the tags with input tags. That worked fine for me.

Thank you for your patience and your great explanations, @tennisagent! And for the $watch hint! I am really grateful for your help :)

That's my solution for the Controller:

angular.controller 'CustomerController', ($scope)   ->

    $scope.customers = [
        { name: "name1" }
        { name: "name2" }
        { name: "name3" }
    ]

    $scope.cards = []

    Card = (@color, @customer, @points, @number, @projectName, @story) ->

    $scope.$watch 'cards', ((newValue) ->
        string = angular.toJson(newValue)
        localStorage["cards"] = string
    ), true

    json = localStorage["cards"]
    parsedCards = angular.fromJson(json) if json?
    $scope.cards = $scope.cards.concat parsedCards if parsedCards?


    $scope.reset = ->
        localStorage.clear()
        sessionStorage.clear()

        $scope.cards = []

    $scope.addCardRed = (customer) ->
        $scope.cards.push new Card("red", customer)

That's my solution for the Markup:

<div class="card card-{{ card.color }}">
    <header>
        <input class="points" contenteditable ng-model="card.points"></input>
        <input class="number" placeholder="#" contenteditable ng-model="card.number"></input>
        <input class="customerName"  contenteditable ng-model="card.customer.name"></input>
        <input class="projectName" placeholder="Projekt" contenteditable ng-model="card.projectName"></input>
    </header>
    <article>
        <input class="task" placeholder="Titel" contenteditable ng-model="card.task"></input>
        <textarea class="story" placeholder="Story" contenteditable ng-model="card.story"></textarea>
    </article>
    <footer>
        <div class="divisions">
            <p class="division"></p>
            <button ng-click="deleteCard()" class="delete">X</button>
        </div>
    </footer>
</div>
<div class="card card-{{ card.color }} backside">
    <article>
        <h2 class="requirement">Requirements</h2>
        <textarea class="requirements" placeholder="Aspects" contenteditable ng-model="card.requirements"></textarea>
    </article>
</div>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top