Firebase makes me stop typing after one keystroke in my AngularFire/AngularJS project - angularjs

I started using Firebase (AngularFire) for synchronizing my data for my application. It's a Card tool for Scrum that adds cards to an array. You can manipulate the input fields.
In the first place I used localStorage, which worked really well. Now that I basically implemented Firebase, I got the following problem: After typing a single key into one field, the application stops and the only way of resuming typing is to click in the input field again.
Do you know why this is? Thank you very much in advance!
That's my basic implementation in my Controller:
Card = (#color, #customer, #points, #number, #projectName, #story) ->
$scope.cards = []
reference = new Firebase("https://MYACCOUNT.firebaseio.com/list")
angularFire(reference, $scope, "cards")
$scope.reset = ->
$scope.cards = []
$scope.addCardRed = (customer) ->
$scope.cards.push new Card("red", customer)
That's my 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>

I ran into this as well. This is because it's recalculating the entire array. Here's how I fixed it:
Bind your input to an ng-model and also add this focus directive
<input class="list-group-item" type="text" ng-model="device.name" ng-change="update(device, $index)" ng-click="update(device, $index)" ng-repeat='device in devices' focus="{{$index == selectedDevice.index}}" />
I set the selectedDevice like this
$scope.update = function(device, index) {
$scope.selectedDevice = device
$scope.selectedDevice.index = index
}
Now create this directive.
angular.module('eio').directive("focus", function() {
return function(scope, element, attrs) {
return attrs.$observe("focus", function(newValue) {
return newValue === "true" && element[0].focus();
});
};
});
Update Sorry for the delay, had a few things to tend to.
The reason why this works is because it is constantly saving the index value of the item in the array you are currently selecting. Once focus is lost, focus is returned immediately by going to that index.
If we're talking about multiple arrays, however, you'll need to refactor the setSelected code to say which array it is.
So you'd want to change
focus="{{$index == selectedDevice.index}}"
to something like
focus="{{$index == selectedDevice.index && selectedDevice.kind == 'points'}}"
Where points is the category of the array where the code appears.

I sorted this one by downloading the most recent version of angularFire.js, seems like bower installed the on that didn't have this fix. now my contentEditable is!

Related

Angular NG-Model not setting

I am trying to set the NG-Model on an input box (within a form if that matters) but it doesn't seem like it ever gets set on $scope. Everytime I set a breakpoint on the if statement in the controller 'lpScan' is always blank. I've tried to display the {{lpScan}} on screen and it also never seems like it sets there. Anybody have any ideas on why that might be?
Here is the small piece of controller code:
$scope.submitLP = function () {
$scope.lpScan = "";
if (!$scope.checkInForm.$valid) {
$scope.formValidate = 1;
return;
}
if ($scope.scanRequired && $scope.lpScan !== $scope.lp.LPNumber) {
FoundationApi.publish('load-notification',
{
title: 'Invalid LP',
content: 'Must scan current LP to receive it.',
autoclose: '4000'
});
}
and here is the html
<div class="full-block">
<form name="checkInForm">
<div class="center data-item">
<div class="button" ng-click="reprintLP()">Reprint LP</div>
</div>
<div class="data-item" ng-if="scanRequired && !lp.CheckedIn"
</div>
<div class="data-label-narrow">Scan LP:</div>
<div class="data-wide"><input id="assignLP" autocomplete="off" type="tel"
ng-keypress="processKeystroke(event)"
ng-model="lpScan" placeholder="Scan LP" name="LPNumber"
required ng-pattern="/^\d+$/" ng-minlength="20" ng-maxlength="20"
ng-trim="true"/>
<div class="data-error"
ng-if="(checkInForm.LPNumber.$dirty || formValidate === 1) && checkInForm.LPNumber.$invalid">
LPNumber must be 20 digits
</div>
</div>
</div>
</form>
First of all you have a broken div in your example code HTML.
<div class="data-item" ng-if="scanRequired && !lp.CheckedIn"
Try adding ng-change on your input something like ng-change="watchInput()" then in your controller add
$scope.watchInput = function() {
console.log($scope.lpScan);
}
Check your console log and see if you are getting a logging at all. If it is then just place your actionable code inside that function.
Looks like you are resetting your $scope.lpScan variable to blank everytime your submitLP function is fired, so declare it outside your submit function:
$scope.lpScan = "";
$scope.submitLP = function () {...
Here is an example:
https://plnkr.co/edit/jcLsGoVFCXkmv1SnUdHl?p=preview

ng-click showing all the hidden text field on ng-repeat rather than one in Angular

I started to work with Angular, it's pretty good to implement, I stuck with a single issue at ng-click
I am getting data dynamically and showing with ng-repeat, and I want to update the data at pencil click and for it I am using input text element, but when I click on pencil It's opening all the text fields
Here is my HTML code
<
div ng-repeat="item in scroller.items track by $index">
<div class="secHead text-center">
<button class="common btnDarkGrey" data-ng-hide="hideCatButton">{{item.category_name}}</button>
<input type="text" id="focus-{{$index}}" class="common btnDarkGrey editDashboardCategory" name="editCategory" value="" data-ng-model="item.category_name" data-ng-show="hideField">
<span data-ng-click="updateCategory(item.category_id,item.category_name,$index)" class="chkOneDone" data-ng-show="hideOkButton">Done</span>
<div class="pull-right">
</div>
</div>
</div>
And here I Angular code
$scope.updateCategory=function(category_id,updated_cat_name, $index){
Category.updateCat($rootScope,$scope,$index,$http,$timeout,updated_cat_name,old_cat_name,category_id);
};
$scope.updatePen=function($index){
old_cat_name=$scope.scroller.items[$index].category_name
$scope.hideField=true;
$rootScope.hideOkButton=true;
$rootScope.hideCatButton=true;
};
I created a Category service to perform task like update
I didn't get any proper solution yet.
Can anybody help me?
Thank You.
If you only want to hide/show one of the elements in the list you need to specify that in some fashion. Right now you have a three rootScope booleans:
$scope.hideField=true;
$rootScope.hideOkButton=true;
$rootScope.hideCatButton=true;
being set for the entire list, and you need to set a show properties on each individual in the list.
In your controller function you can do something like this before you expect a click:
//normal for loop so that you have the index
for(var i=0; i < $scope.scroller.items.length; i++){
$scope.scroller.items[i].show = false;
}
Then you can do something like this to actually show the fields:
HTML:
div ng-repeat="item in scroller.items track by $index">
<div class="secHead text-center">
<button class="common btnDarkGrey" ng-hide="!item.show">
{{item.category_name}}</button>
<input type="text" id="focus-{{$index}}" class="common btnDarkGrey editDashboardCategory" name="editCategory" value="" ng-model="item.category_name" ng-hide="!item.show">
<span data-ng-click="updateCategory(item.category_id,item.category_name,$index)" class="chkOneDone" ng-show="item.show">Done</span>
<div class="pull-right">
</div>
</div>
</div>
Controller:
//controller declaration --
$scope.updatePen = function(index){
$scope.scroller.items[index].show = true;
};
It's my understanding that you need all three properties to show once a click happens, so I condensed all the show properties into one single show property.
Your view only sees that hideField is true and performs that action for all of the items in your array. I hope this helps!

Is it possible to delete all scope variable of a controller? AngularJS

I am new to AngularJS and i dont know is it possible to delete all scope variables of a controller.I am using ng-controller with ng-repeat, like this.
<div ng-controller="main">
<div ng-repeat="x in list" ng-controller="test">
<input type="text" ng-model="text">
<span ng-click="remove($index)"> x </span>
<div>
</div>
JS
myapp.controller('main',function($scope){
$scope.list=[1,2,3,4]
})
myapp.controller('test',function($scope){
$scope.text="untitiled"
})
I want to remove the clicked scope.Can anyone help me or please suggest me a better way. Thanks
The question isn't very clear, but it looks like you may want to remove the item after clicking. Since you are passing into the remove function the index, you can splice it out. The DOM will autoupdate and remove that from the list:
$scope.remove = function(i) {
$scope.list.splice(i,1);
console.log($scope.list);
}
In the event you are doing something different in that you only want to hide it, you would push the index onto another array and then use something like ng-show or ng-hide.
$scope.remove2 = function(i) {
$scope.hideList.push(i);
}
$scope.shouldHide = function(i) {
return $scope.hideList.indexOf(i)!=-1;
}
<div ng-repeat="number in list2" >
{{number}}
<span ng-hide='shouldHide($index)' ng-click="remove2($index)"> x </span>
</div>
Here is a simple example of both scenarios. In real life, usually we are dealing with arrays of objects and what you might be doing is setting a property on one of the objects to hidden and controlling it that way.
Demo: http://plnkr.co/edit/G7UINKUCBJ4yZhQNtuJ2?p=info
If you actually want to remove all the keys from the scope:
function removeKeys() {
for(key in $scope) {
if (key.substr(0,1)!='$' && key!='this')
delete $scope[key];
}
}

LocalStorage of contenteditable content (AngularJS)

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!
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.
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>

duplicates in a repeater are not allowed angular

I'm trying to create a form like below, this using ng-repeat directive in angular and it whenever I created a new row complains
"Duplicates in a repeater are not allowed.".
While I understand the solution for this is by putting "track by $index", however it causes another issue, which clicking delete on one row deletes the value of other field. So I suspect that track by index is OK for static text but not input form. So how to use ng-repeat correctly for my case?
My jsfiddle : demo.
Edit : I do aware that json array of object will solve my issue ( because for object angular create $$hashKey ) and already implemented this for most of my other module. But I am actually expecting some fix that can be done without really change my json array of string. Sorry for not being clear.
My current code :
HTML
<div class="row-fluid spacer10">
<a ng-click="addAKA()" class="btn btn-primary spacer5 left30"><i class="icon-plus icon-white"></i> Add New Alias</a>
</div>
<div class="row-fluid spacer10"></div>
<div class="row-fluid spacer5" ng-repeat="item in aliasList track by $index">
<input type="text" class="span6 left30" ng-model="item">
<button class="btn btn-danger" ng-click="deleteAKA($index)">delete</button>
<BR/>
</div>
Javascript
$scope.addAKA = function ()
{
if($scope.aliasList == null)
{
$scope.aliasList = [];
}
$scope.aliasList.push("");
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
$scope.deleteAKA = function (idx)
{
var aka_to_delete = $scope.aliasList[idx];
$scope.aliasList.splice(idx, 1);
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
I would guess this is caused when there are more than one empty strings in the list.
If this is the case, it is caused because any two empty strings are equals in JS and Angular repeater does not allow duplicate values (as clearly stated in the message). This is a valid decision as they have to relate an object in the list with its DOM tree to minimize DOM manipulation.
A solution would be to insert simple objects containing the string in the model:
$scope.addAKA = function () {
...
$scope.aliasList.push({value:""});
...
};
And adjust your template:
<input type="text" class="span6 left30" ng-model="item.value">
Since all new objects are different, your problem should be solved.
See a fiddle where a filter is implemented to transform the model back to a list of strings.
When you type in a new created input, your list stays the same. Angular on any list change will update the view (ng-repeat) and remove all new stored text. Therefore we need to add ng-change to update our list on any input change
Add ng-change="change(i, $index) to your item and it should work
HTML
<div ng-controller='ctrl'>
<ol>
<li ng-repeat='i in list track by $index'>
<input type='text' ng-model='i' ng-change="change(i, $index)"></input>
<button ng-click='deleteItem($index)'>Delete</button>
</li>
</ol>
<button ng-click='addItem()'>Add</button>
<div>ITEM: {{list | json}}</div>
</div>
Javascript
angular.module("app", []).controller("ctrl", function ($scope) {
$scope.list = ["one","two"];
$scope.addItem = function ()
{
$scope.list.push("");
};
$scope.deleteItem = function (idx)
{
var item_to_delete = $scope.list[idx];
$scope.list.splice(idx, 1);
};
$scope.change = function (item, idx)
{
$scope.list[idx] = item;
};
});
See fixed Demo in DEMO
Yes, pushing more than one empty string will result in ng-repeat complaining.
In addition, you can also try:
if ($scope.aliasList.indexOf(VALUE_TO_ADD) === -1) {
...
}

Resources