Best way to save/update a resource with ($watch or save button, etc..) - angularjs

Currently I run into the following problem,
we are working on a form heavy application and wanted a good user experience so we tried to stop have a save / update button everywhere.
Currently we try to $watch every Form Change, but this won't work correctly since it updates the scope when the model gets updated which causing problems on Decimal / Money Values.
What would you prefer? Still the messy save button or maybe something like Gmail did?
What are good methods to do this without save buttons.
/* EDITED */
Currently we use this method to update our form.
It first copys the scope in an object and checks if it is the same than the object that is set after the date got pulled.
$scope.$watch('task', function(scope) {
console.log($scope.updateForm);
scopeObject = angular.copy(scope);
if(scope !== undefined) {
if(!(_.isEqual(scopeObject, mainObject))){
//scope_copy.request_date = $filter('date')(new Date(scope.request_date), 'fullDate');
console.log('update');
scope.$update({pk: $routeParams.taskId}, function() {
scope.request_date = $filter('date')(scope.request_date);
mainObject = angular.copy(scope);
});
mainObject = angular.copy(scope);
}
}
}, true);
currently i think this code is somehow messy since it can't update decimal numbers.
but currently i don't have a better solution. (i don't want to use a Button to submit the form, it should be done interactivly).

Related

Why calling different subscription on the same collection cause error in meteor?

I working on this angular-meteor tutorial step 12
an I have a question in
Stopping a subscription Topic
you can use ctrl+f using "meteorSubscribe"
then the key sentence on that topic is
The reason is that we are calling a different subscription on the same collection inside the partyDetails controller.
the code before correction is
$scope.party = $meteor.object(Parties, $stateParams.partyId).subscribe('parties');
$scope.users = $meteor.collection(Meteor.users, false).subscribe('users');
then after correction
$scope.party = $meteor.object(Parties, $stateParams.partyId);
$scope.users = $meteor.collection(Meteor.users, false).subscribe('users');
$scope.$meteorSubscribe('parties');
I try to run before correction code and nothing(error) show in cmd but it just cause the wrong result as tutorial say
if you navigate to the party details page and then go back, pagination and search will stop working.
Then i got two question
Why no error show on cmd?
Why error from partyDetails controller affect to partiesList controller search and pagination? What is their relation?
EDIT: If you don't cancel a subscription, then if you navigate away and back again you will end up trying to subscribe twice to the same publication, resulting in the error, because subscriptions in meteor last until you end them.
There are two ways to get rid of a subscription with angular-meteor. One you can assign a handle variable to the subscription and then on navigating away from the page you can stop it. Two (the recommended way) is to use $scope.$meteorSubscribe instead of $meteor.subscribe() because it is set up to automatically remove the subscription when the scope is destroyed.
I can't see all of your code to know for sure why you are or are not getting the errors you think you should, hopefully this sheds some light on what is going on in the tutorial.
The very end result would be something like:
$meteor.autorun($scope, function() {
$meteor.subscribe('parties', {
limit: parseInt($scope.perPage),
skip: parseInt(($scope.page - 1) * $scope.perPage),
sort: $scope.sort
}).then(function() {
$scope.partiesCount = $meteor.object(Counts, 'numberOfParties', false);
$scope.parties = $meteor.collcetion(function() {
return Parties.find({}, {
sort: $scope.getReactively('sort');
});
});
});
});
Notice that he's also changing the publish function on the server. It helps to understand if you click the links to show the git diffs.

How to reactively get currently logged-in user in angular-meteor and ionic?

I want $scope.me to be reactively represent the currently logged-in user, so that when a user logs out and logs back in as another, this variable is updated. Right now when a user logs out and logs back in as another user, the old value of $state.me persists. Once the page is reloaded this value is corrected. How do I fix this?
Here is my working, crappy solution in the controller:
$scope.$on('$ionicView.enter', function(e) {
if($rootScope.currentUser)
$scope.me = $rootScope.currentUser;
});
This worked, but reset the variable every time the user transitioned to this state... an ugly non-meteor solution.
Here is my current attempt:
$scope.me = ($scope.$meteorCollection(function(){
return Meteor.users.find({_id: Meteor.userId()});
}))[0];
This should work, since Meteor.userId() is reactive, and should force it's parent function to re-run when it changes, thus correcting $scope.me in realtime... but it doesn't.
Instead $scope.me is updated to the sanitized profile of the old user... meaning nothing but their _id and profile are visible. This is telling me that $scope.$meteorCollection is rerunning, but with an old value of Meteor.userId().
What am I missing? Thank you!
* EDIT *
Here's a twist
$scope.me = ($scope.$meteorCollection(function(){
console.log("$scope.me reset to "+Meteor.userId());
return Meteor.users.find({_id: Meteor.userId()});
}))[0];
prints the NEW user's ID to console when the user switches, but still returns the old user even though the query is re-run with correct values.
Have you tried using $rootScope.currentUser instead of trying to bake your own solution? Angular-Meteor automatically creates this $rootScope object for you, and the docs say that it is reactive.
Another possible solution would be to use the $meteor.autorun convenience method, but instead of autorunning based on a change in a $scope variable, you could have something like if(Meteor.userId()) or if(Meteor.user()).
As a matter of fact, this is what Angular-Meteor is doing under the covers anyway if you look at the source code.
From the docs:
// Updated to check for deep equality on the getReactively() call
$meteor.autorun($scope, function() {
$scope.userForScore = $scope.$meteorObject(Userinfo,
{user_id: $scope.getReactively('currentUser', true)._id}
);// Ultimately searches up scope chain for $rootScope.currentUser._id
});
I have found a solution:
$meteor.autorun($scope, function(){
var user = (Meteor.users.find({_id: Meteor.userId()}).fetch())[0];
if( user != null ){
$scope.me = user;
}
});
$meteor.autorun automatically reruns reactive dependencies included within the function. In this case, Meteor.userId(). Thus whenever that is changed by meteor, the function body re-runs, setting $scope.me to the current user if one is logged in.
Thank you to JacobWuzHere for the tips!

AngularJS typeahead select on blur

I'm using typeahead through in my AngularJS project and I would like to have it select the entry if I type the full value and click out of the field.
I've put together an example of what I mean
http://plnkr.co/edit/NI4DZSXofZWdQvz0Y0z0?p=preview
<input class='typeahead' type="text" sf-typeahead options="exampleOptions" datasets="numbersDataset" ng-model="selectedNumber">
If I type in 'two' and click on 'two' from the drop down then I get the full object {id: 2, name: 'two'}. This is good, if however I type 'two' and click to the next field without selecting is there a way to accept the top of the list on loss of focus on a text field?
I'm not sure if I'd want to have that sort of functionality in my app. The user hasn't actually selected anything. So selecting something for them would introduce frustrations.
But I do understand that often odd requirements are needed. In this case, I'd attack it using ngBlur. Assign a function to be called on blur. You can grab the contents of ng-model and then loop through your data (assuming static & not being sent via server) to find a match.
You can most likely just look at the source code of your typeahead directive and strip out the part does the comparison and then choose the first item in the array.
Unfortunately the underlying component does not emit any events for this condition. This will make the solution more complex. However when the value is being entered and the Typehead magic has happened you can supplement those events and catch them to update your ngModel.
I have created a plnkr based on your plnkr and although have not cleaned up but it is a working plnkr doing by far what you need.
The gist of this is following code however you can put this code wherever best suited
The explanation below:
//Crux - this gets you the Typeahead object
var typeahead = element.data('ttTypeahead');
//This gets you the first
var datum = typeahead.dropdown.getDatumForTopSuggestion();
if (datum){
//you can do lot of things here however
//..I tried to - fill in the functionality best suited to be provided by Typeahead
//for your use case. In future if Typeahead gets this
//..feature you could remove this code
typeahead.eventBus.trigger("hasselections", datum.raw, datum.datasetName);
}
In the above code you can also save the datum somewhere in the scope for doing whatever you like with it later. This is essentially your object {num: 'Six'} Then you may also use ngBlur to set it somewhere (however the plnkr I created doe snot need these gimmicks.)
Then further down - ngModel's value is set as below
element.bind('typeahead:hasselections', function(object, suggestion, dataset) {
$timeout(function(){
ngModel.$setViewValue(suggestion);
}, 1);
//scope.$emit('typeahead:hasselections', suggestion, dataset);
});
I'm with EnigmaRM in that ngBlur seems to be the way to do what you want. However, I agree with the others that this could be somewhat strange for the end users. My implementation is below (and in plnkr). Note that I trigger on ngBlur, but only apply the model if and only if there is only one match from Bloodhound and the match is exact. I think this is probably the best of both worlds, and hope it should give you enough to go on.
$scope.validateValue = function() {
typedValue = $scope.selectedNumber;
if(typedValue.num !== undefined && typedValue.num !== null)
{
return;
}
numbers.get(typedValue, function(suggestions) {
if(suggestions.length == 1 && suggestions[0].num === typedValue) {
$scope.selectedNumber = suggestions[0];
}
});
};

angularjs - streamline form (automatic) submission based on dirty scope

Problem space
I have a problem where I'm submitting a form based on criteria being fulfilled, rather than having a form submission button.
Let's say I have 3 drop downs, the first two are grouped but one needs to be selected, meaning I can select one or the other but I can't leave them empty, the 3rd one is a required field.
After that, the page automatically fetches in results.
Lets say I have checkboxes and a few more dropdowns. Any future selections on the 3 dropdowns mentioned, checkboxes, and dropdowns automatically filters the results.
What I know
Now after reading angular documentation, I was checking up on $dirty, $pristine and operations on both, like $setDirty and $setPristine; however, it seems that this is for a FormController
So I'm assuming this is useful for an entire scope. I didn't see any inclination that I can figure out for selected scopes.
What I have so far
So basically, I was hoping that I'd be making use of the scope's tracking features, but I don't know much about it. I created a single controller for my application and a single scope, since that's what seemed easiest for me. I have 3rd party plugins that play a role into the scope like:
$scope.3rdpartyConfig = {
prop1: [],
prop2: getData()
}
I don't think something like that would be useful in checking to see form submission if I was going to check the $dirty state of my form.
Then I thought about the old way I used to do things, but "angularlizing" it:
so I'd have something like:
<input type="checkbox" ng-model="state.Checked" ng-change="checkIfWeCanSubmitThenSubmit()" id="ng-change-example1" />
So I'd be having ng-changes and ng-clicks all over my html form, hitting that function, where the function would look like this pseudocode:
$scope.checkIfWeCanSubmitThenSubmit= function() {
var validated = false;
//check to see if dropdown1 or dropdown2 are selected
//check to see if dropdown3 is selected
// add more here per requirement
//if the above are true, then validated = true
if (validated)
{
//add dropdown4 and 5, and checkbox groups into filter
}
submit();
}
But I was thinking this isn't the angular way of doing things since this certainly isn't facilitated.
I was hoping that the scope would offer some kind of way, where I can check to see what pieces of my scope is dirty or not before I can submit and fetch data, or if there is a better way than appending this function to every html element; like having some kind of scope tracker that I can check up on and watch.
Which reminds me, I don't want to have a series of $scope.$watch either, its just that it'd be way too much work to bind to every piece of html code, unless there's way to watch the scope of a collection of specific scope variables, then, I wouldn't mind.
like (forgive the pseudocode):
$scope.$watch('dropdown1, dropdown2, dropdown4', function(dirty, pristine)
{
if (dirty)
{ blah blah blah }
});
Edit (2/28/2013):
I tried doing it this way:
$scope.masterCriteria =
[
{ DropDown1: $scope.AppModel.Dropdown1},
{ DropDown2: $scope.AppModel.Dropdown2 },
{ DropDown3: $scope.AppModel.Dropdown3 },
{ Checkbox1: $scope.AppModel.Checkbox1 },
{ Checkbox2: $scope.AppModel.Checkbox2 }
];
$scope.$watch('masterCriteria', function (newVal) {
if (newVal) { logger.info("did I change?"); }
}, true);
The watcher detected nothing, and any values I changed to the scope of AppModel wasn't being picked up in the $watch. Was worth a try, still trying to figure this out.
You can slightly change your model and group fields related to input form together. Put them into single object. Like this:
$scope.state = { checkbox1: false, checkbox2: true, ... }
Later bind input boxes to field of state object:
<input ng-model="state.checkbox1" ... >
And watch state object to catch all updates of nested fields:
$scope.$watch('state', ...
JsFiddle example here

Changes in Page/Dirty Page Backbone/MarioneteJS

I have the following question:
I have an application that I'm writing with Backbone/MarioneteJS, and I have the following issue, maybe is a simple and I'm complicating the issue.
In the application you can edit some fields and then save them, I want to be available that when the user edit some filed and he tries to navigate to another place, the application will block/inform him that he have unsaved changes.
In the past not in Backbone/MarioneteJS, we use a global variable that I checked before leaving to another place, something like(pseudocode like):
var dirtyPage = false;
// when editing we do
if (editing) {
dirtPage = true;
}
// In another part of the code before navigating to another place
if (dirtPage) {
ShowMessage("Unsaved Changes");
} else {
Navigate(AnotherPlace);
}
I know that if I do it like below it will work, but maybe there is a more elegant way to resolve the issue.

Resources