I'm sing the following ng-click function to create groups:
$scope.createGroup = function() {
AV.Cloud.run('createUserGroup', {
groupName: $scope.initName
}, {
success: function(result) {
var data = JSON.parse(result);
var json = {
id: data.objectId,
name: data.name,
users: 0,
createdAt: data.createdAt
}
$scope.groups.push(json);
$scope.initName = "";
$scope.$digest();
},
error: function(error) {
alert(error.message);
}
});
But then when I click the link to go to the group (which is in the same page) the group doesn't appear. I have to refresh the browser in order to make it appear. I tried $scope.$digest() and $scope.$apply() but they didn't solve the problem, only location.reload(); does.
Does AngularJS have another function to make an update or reload that might solve this problem?
EDIT:
This is how I'm displaying the group:
<div id="group-new" class="col-md-6">
<h2>{{group.name}}</h2>
</div>
Have you tried wrapping it up in $scope.$apply, it should both apply the changes to your $scope and digest afterwards. Example:
$scope.createGroup = function() {
AV.Cloud.run('createUserGroup', {
groupName: $scope.initName
}, {
success: function(result) {
var data = JSON.parse(result);
var json = {
id: data.objectId,
name: data.name,
users: 0,
createdAt: data.createdAt
}
$scope.$apply(function() {
$scope.groups.push(json);
$scope.initName = "";
});
},
error: function(error) {
alert(error.message);
}
});
How did you use $scope.$apply?
Did you try?
success: function(result) {
// ...
$scope.$apply(function () {
$scope.groups.push(json);
$scope.initName = "";
});
}
Maybe there is already a $digest cycle running?
Try $timeout (you have to inject it into to your controller):
$timeout
success: function(result){
// ...
$timeout(function(){
$scope.groups.push(json);
scope.initName = "";
});
}
Basically it delays the execution (in the above case: 0ms) of the inner function and triggers a new digest cycle with $apply. It does not create "$digest already in progress" errors because it starts a new digest after the running one is finished.
Related
EDIT
This is the log for my request
$("#search_button").click(function(){
var search = document.getElementById('search_text').value;
$.post('http://127.0.0.1:5000/search', {search_text: search, num_results: 2}, function(data, textStatus, xhr) {
console.log(data);
});
});
Background
I'm fetching some data from my server and trying to display it on my page using the Onsen UI framework's Infinite List but I'm getting a cannot access property 'text' of undefined error. I do see the data using console.log(data) so I hope there's not a problem with the request. I would really appreciate if someone could explain me what am I doing wrong here? Thanks
This Works
I tried a basic example before fetching the data
ons.ready(function() {
var data = [{"text":"Title 1"}, {"text":"Title 2"}]
var infiniteList = document.getElementById('infinite-list');
infiniteList.delegate = {
createItemContent: function(i) {
return ons.createElement(`<ons-list-item>
<p style="color:DodgerBlue;">`+data[i].text+`</p>
<img style="width:100%;" src="http://images.all-free-download.com/images/graphiclarge/beautiful_scenery_04_hd_pictures_166258.jpg"/>
</ons-list-item>`);
},
countItems: function() {
return data.length;
}
};
infiniteList.refresh();
});
This doesnt work
ons.ready(function(){
$("#test_button").click(function(){
$.post('http://127.0.0.1:5000/search', {search_text: 'car', num_results: 2}, function(data, textStatus, xhr) {
/*after success */
var infiniteList = document.getElementById('infinite-list');
infiniteList.delegate = {
createItemContent: function(i) {
return ons.createElement(`<ons-list-item>
<p style="color:DodgerBlue;">`+data[i]+`</p>
<img style="width:100%;" src="http://images.all-free-download.com/images/graphiclarge/beautiful_scenery_04_hd_pictures_166258.jpg"/>
</ons-list-item>`);
},
countItems: function() {
return 2;
}
};
infiniteList.refresh();
});
});
});
'Data' is limited to the function in which it is visible; it needs to be recognized where you are using it as well.
Use another variable which is declared outside both functions.
var dataUse = [];//----------declare here so it is visible in every scope
ons.ready(function(){
$("#test_button").click(function(){
$.post('http://127.0.0.1:5000/search', {search_text: 'car', num_results: 2}, function(data, textStatus, xhr) {
/*after success */
dataUse = data;
var infiniteList = document.getElementById('infinite-list');
infiniteList.delegate = {
createItemContent: function(i) {
return ons.createElement(`<ons-list-item>
<p style="color:DodgerBlue;">`+dataUse[i]+`</p>
<img style="width:100%;" src="http://images.all-free-download.com/images/graphiclarge/beautiful_scenery_04_hd_pictures_166258.jpg"/>
</ons-list-item>`);
},
countItems: function() {
return 2;
}
};
infiniteList.refresh();
});
});
});
I have two services where one needs to query the other by an id for example.
This is what I currently have but I'm getting $digest iteration limits.
"Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
Controller
angular.module('exampleApp')
.controller('ExampleCtrl', function ($scope, PeopleService, PlanService) {
$scope.plan = new Plan();
// or
$scope.plan = Plan.get({ id: 1 });
// Used to populate a <select> menu in the view
PeopleService.all().then(function(data) {
$scope.people = data;
});
});
PlanService
angular.module('exampleApp')
.factory('PlanService', function($resource, PeopleService){
// Resource example
// { id: 1, title: 'Example', assignee_id: 1 }
var Plan = $resource('/plans/:id', { id: '#id' }, {
'query': { method:'GET', isArray: true },
'get': { method:'GET', isArray: false },
'update': { method:'PUT' }
});
// Return the matching person from PeopleService
Plan.prototype.assignee = function() {
if(this.assignee_id) {
return PeopleService.find_by_id(this.assignee_id)
} else {
return null;
}
}
return Plan;
});
PeopleService
angular.module('exampleApp')
.service('PeopleService', function($http, $q){
var people_array = $q.defer();
// This list is reasonably static hence why I feel no need to make it it's own resource
// Contains an array of people objects for example
// { id: 1, name: 'Paul Smith', hourly_rate: 5.0, max_hours: 8 }
$http.get('/people/json').then(function(result) {
people_array.resolve(result.data);
});
this.all = function() {
return people_array.promise;
};
this.find_by_id = function(id) {
var def = $q.defer();
people_array.promise.then(function(data) {
// underscore.js find
var person = _.find(data, function(person) {
return parseInt(person.id) === parseInt(id);
})
def.resolve(person);
});
return def.promise;
};
});
View
<select name="person_id" class="form-control" ng-model="plan.assignee_id" ng-options="assignee.id as assignee.name for person in people" required>
</select>
{{ plan.assignee().name }}
Now the select populates no problem, however the plan.assignee.name causes the digest errors when using the select menu to select an option. I'm sure this is a misunderstanding I have with how promises work so I'd appreciate a nudge in the right direction.
I have the following code:
var Panel = React.createClass({
getInitialState: function () {
return {
user_id: null,
blogs: null,
error: false,
error_code: '',
error_code: ''
};
},
shouldComponentUpdate: function(nextProps, nextState) {
if (nextState.error !== this.state.error ||
nextState.blogs !== this.state.blogs ||
nextState.error_code !== this.state.error_code
) {
return true;
}
},
componentDidMount: function() {
var self = this;
var pollingInterval = setInterval(function() {
$.get(self.props.source, function(result) {
if (self.isMounted()) {
self.setState({
error: false,
error_code: '',
error_message: '',
blogs: result.user.blogs,
user_id: result.user.id
});
}
}.bind(self)).fail(function(response) {
self.setState({
error: true,
error_code: response.status,
error_message: response.statusText
});
}.bind(self));
}, 1000);
},
render: function() { ... }
});
The important part to focus on is the componentDidMount This will fetch every second, regardless if there is an error or not. The render function, assuming theres an error, will display the appropriate method. So for all intense and purpose, this code does exactly what I want it to do, it fetches, if it fails, it fetches again until it succeeds.
But I need to make some changes, and this is where I am lost. I want to say: Fetch once, pass or fail - it doesn't matter. THEN every 15 seconds after that initial fetch, try again - regardless of pass or fail
I would normally spin up a backbone collection and router along with a poll helper to do all this, but in this specific case there is no need for the extra overhead. So thats where I am stumped. How do I accomplish what I am trying to achieve?
You should be able to just refactor your code a bit to be able to call your polling function a few different ways (like manually for example and then at a specified interval):
componentDidMount: function() {
this.startPolling();
},
componentWillUnmount: function() {
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
},
startPolling: function() {
var self = this;
setTimeout(function() {
if (!self.isMounted()) { return; } // abandon
self.poll(); // do it once and then start it up ...
self._timer = setInterval(self.poll.bind(self), 15000);
}, 1000);
},
poll: function() {
var self = this;
$.get(self.props.source, function(result) {
if (self.isMounted()) {
self.setState({
error: false,
error_code: '',
error_message: '',
blogs: result.user.blogs,
user_id: result.user.id
});
}
}).fail(function(response) {
self.setState({
error: true,
error_code: response.status,
error_message: response.statusText
});
});
}
Hello again everyone.
EDIT: I want to emphasize that I can find no docs on the solution for this.
I am using a route to perform a search query to my server. The server does all the data logic and such and returns a list of objects that match the keywords given. I am taking those results and feeding them to the model so that I can use the {{#each}} helper to iterate over each result.
The problem I am having is that the model does not want to refresh when the searchText (search input) changes. I've tried several things. I'm not worried about creating too many ajax requests as my server performs the search query in 2ms. Here's what I have now.
App.SearchView = Ember.View.extend({...
EDIT:
Thank you for the answer.
App.SearchView = Ember.View.extend({
didInsertElement: function () {
this._super();
Ember.run.scheduleOnce('afterRender', this, this.focusSearch);
},
focusSearch: function () {
$(".searchInput").focus().val(this.get("controller").get('searchTextI'));
}
});
App.SearchRoute = Ember.Route.extend({
model: function () {
return this.controllerFor('search').processSearch();
}
});
App.SearchController = Ember.ArrayController.extend({
searchTextI: null,
timeoutid: null,
processid: null,
updateSearch: function () {
if(this.get('timeoutid')) {clearTimeout(this.get('timeoutid')); }
var i = this.get('searchTextI');
var sc = this;
clearTimeout(this.get('processid'));
this.controllerFor('index').set('searchText', i); //set the search text on transition
if(i.length < 3) {
this.set('timeoutid', setTimeout(function () {
sc.controllerFor('index').set("transitioningFromSearch", true);
sc.transitionToRoute('index');
}, 1500));
} else {
var self = this;
this.set('processid', setTimeout(function() {
self.processSearch().then(function(result) {
self.set('content', result);
});
}, 1000));
}
}.observes('searchTextI'),
processSearch: function () {
return $.getJSON('http://api.*********/search', { 'token': guestToken, 'search_query': this.get('searchTextI') }).then(function(data) { if(data == "No Results Found.") { return []; } else { return data; } }).fail(function() { return ["ERROR."]; });
}
});
Don't observe anything within a route and don't define any computed properties. Routes are not the place for these. Apart from that, the model doesn't fire because controller is undefined.
One way to achieve what you want:
App.SearchRoute = Ember.Route.extend({
model: function () {
this.controllerFor('search').searchQuery();
}.observes('controller.searchText') //not triggering an ajax request...
});
App.SearchController = Ember.ArrayController.extend({
searchQuery: function() {
return $.getJSON('http://api.**************/search', { 'token': guestToken, 'search_query': t }).fail(function() {
return null; //prevent error substate.
});
}
onSearchTextChange: function() {
var controller = this;
this.searchQuery().then(function(result) {
controller.set('content', result);
});
}.observes('searchText')
});
Putting an observes on the model hook is not going to do anything. You should simply do what you were thinking of doing and say
processSearch: function () {
this.set('content', $.getJSON....);
}
Which interface or component do you suggest to display the state of parallel async calls? (The language is not so important for me, just the pattern, I can rewrite the same class / interface in javascript...)
I load model data from REST service, and I want to display pending label before the real content, and error messages if something went wrong... I think this is a common problem, and there must be an already written component, or best practices, or a pattern for this. Do you know something like that?
Here is a spaghetti code - Backbone.syncParallel is not an existing function yet - which has 2 main states: updateForm, updated. Before every main state the page displays the "Please wait!" label, and by error the page displays an error message. I think this kind of code is highly reusable, so I think I can create a container which automatically displays the current state, but I cannot decide what kind of interface this component should have...
var content = new Backbone.View({
appendTo: "body"
});
content.render();
var role = new Role({id: id});
var userSet = new UserSet();
Backbone.syncParallel({
models: [role, userSet],
run: function (){
role.fetch();
userSet.fetch();
},
listeners: {
request: function (){
content.$el.html("Please wait!");
},
error: function (){
content.$el.html("Sorry, we could not reach the data on the server!");
},
sync: function (){
var form = new RoleUpdateForm({
model: role,
userSet: userSet
});
form.on("submit", function (){
content.$el.html("Please wait!");
role.save({
error: function (){
content.$el.html("Sorry, we could not save your modifications, please try again!");
content.$el.append(new Backbone.UI.Button({
content: "Back to the form.",
onClick: function (){
content.$el.html(form.$el);
}
}));
},
success: function (){
content.$el.html("You data is saved successfully! Please wait until we redirect you to the page of the saved role!");
setTimeout(function (){
controller.read(role.id);
}, 2000);
}
});
}, this);
form.render();
content.$el.html(form.$el);
}
}
});
I created a custom View to solve this problem. (It is in beta version now.)
Usage: (Form is a theoretical form generator)
var content = new SyncLabelDecorator({
appendTo: "body",
});
content.load(function (){
this.$el.append("normal html without asnyc calls");
});
var User = Backbone.Model.extend({
urlRoot: "/users"
});
var UserSet = Backbone.Collection.extend({
url: "/users",
model: User
});
var Role = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: 'members',
relatedModel: User
}]
});
var administrator = new Role({id :1});
var users = new UserSet();
content.load({
fetch: [role, users],
sync: function (){
var form = new Form({
title: "Update role",
model: role,
fields: {
id: {
type: "HiddenInput"
},
name: {
type: "TextInput"
},
members: {
type: "TwoListSelection",
alternatives: users
}
},
submit: function (){
content.load({
tasks: {
save: role
},
sync: function (){
this.$el.html("Role is successfully saved.");
}
});
}
});
this.$el.append(form.render().$el);
}
});
Code:
var SyncLabelDecorator = Backbone.View.extend({
options: {
pendingMessage: "Sending request. Please wait ...",
errorMessage: "An unexpected error occured, we could not process your request!",
load: null
},
supported: ["fetch", "save", "destroy"],
render: function () {
if (this.options.load)
this.load();
},
load: function (load) {
if (load)
this.options.load = load;
this._reset();
if (_.isFunction(this.options.load)) {
this.$el.html("");
this.options.load.call(this);
return;
}
_(this.options.load.tasks).each(function (models, method) {
if (_.isArray(models))
_(models).each(function (model) {
this._addTask(model, method);
}, this);
else
this._addTask(models, method);
}, this);
this._onRun();
_(this.tasks).each(function (task) {
var model = task.model;
var method = task.method;
var options = {
beforeSend: function (xhr, options) {
this._onRequest(task, xhr);
}.bind(this),
error: function (xhr, statusText, error) {
this._onError(task, xhr);
}.bind(this),
success: function (data, statusText, xhr) {
this._onSync(task, xhr);
}.bind(this)
};
if (model instanceof Backbone.Model) {
if (method == "save")
model[method](null, options);
else
model[method](options);
}
else {
if (method in model)
model[method](options);
else
model.sync(method == "fetch" ? "read" : (method == "save" ? "update" : "delete"), model, options);
}
}, this);
},
_addTask: function (model, method) {
if (!_(this.supported).contains(method))
throw new Error("Method " + method + " is not supported!");
this.tasks.push({
method: method,
model: model
});
},
_onRun: function () {
this.$el.html(this.options.pendingMessage);
if (this.options.load.request)
this.options.load.request.call(this);
},
_onRequest: function (task, xhr) {
task.abort = function () {
xhr.abort();
};
},
_onError: function (task, xhr) {
this._abort();
this.$el.html(this.options.errorMessage);
if (this.options.load.error)
this.options.load.error.call(this);
},
_onSync: function (task, xhr) {
++this.complete;
if (this.complete == this.tasks.length)
this._onEnd();
},
_onEnd: function () {
this.$el.html("");
if (this.options.load.sync)
this.options.load.sync.call(this);
},
_reset: function () {
this._abort();
this.tasks = [];
this.complete = 0;
},
_abort: function () {
_(this.tasks).each(function (task) {
if (task.abort)
task.abort();
});
}
});