onsen ui2 - master detail page with angularjs - onsen-ui2

how can i create a master detail page with angularjs. I created a factory which calls the data from a json file then created a list controller to list the title for the list items but when i reach the view controller im getting an error. an example of the code is below
var cachedData;
function getData($scope, callback) {
$http.get('js/husbandry.json').success(function(data) { // call data from json file
$scope.items = data.items;
callback(data.items);
console.log(data);
}).error(function() {
ons.notification.alert({
message: 'Could not Connect to the Database.'
});
});
}
return {
list: getData,
find: function(data, callback) {
var hb = cachedData.filter(function(items) {
return items.title == data;
})[0];
callback(hb);
}
};
});
module.controller('ListCtrl', function($scope, HB) {
$scope.items = {
title: 'day 2'
}
HB.list($scope.items.title, function(items) {
$scope.items = items;
});
$scope.showDetail = function(title) {
$scope.navi.pushPage("day1.html", { animation: "fade", items: title });
}
});
module.controller('ViewCtrl', ['$scope', 'HB', function($scope, HB) {
var page = $scope.navi.topPage.data;
HB.find(page.options.data, function(hb) {
$scope.items = hb;
});
}]);

Related

How to stop Kendo UI dropdown from sorting alphabetically

I have been given the fun job on taking on someone elses code and trying to figure it out...
I have been tearing my hair out trying to figure out how to to stop a Kendo UI dropdown from sorting alphabetically ?
<select id="ddlArtworkStatuses" onlyPreparedValues="true"
readOnlyInput="true" data-bind="idNameOptions: artworkStatuses,
comboboxSelectedValue: artworkStatusId"></select>
For some reason this sorts althabetically but I want it to keep the order in the datasource. I have double checked to make sure it is in the order I want.
It doesn't use the standard way of using Kendo UI and I can't find any examples anywhere of using it this way?
It binds the view to the model with this line at the top of the view I believe?
<!-- ko with: fabricationModel -->
The vm.fabrication file is this:
(function (app) {
app.namespace("models");
var resources = {
unableToDeleteArtworkMessage: "This artwork cannot be deleted because it is linked to one or more of the following: Movements, Shows, POs. Or it has Imported Costs, Sales or Restoration Cases.",
unableToBlankArtworkFullyMessage: "This artwork has some data that will not be blanked if you continue. This could be one or more of the following: Movements, Shows, POs, Imported Costs, Sales or Restoration Cases. Do you want to continue?",
deleteArtworkTitle: "Delete artwork",
blankArtworkTitle: "Blank Artwork",
duplicateTitle: "New artwork form is pre-populated",
duplicateMessage: "You can make changes to Fabrication form fields. Record will be created after you press save button",
saveError: 'An error occured during artwork saving. Changes were not saved.'
};
app.models.FabricationModel = function (datasource) {
var emitter = new app.events.EventEmitter(),
self = this,
duplicateArtwork = ko.observable(false);
this.isNewArtwork = ko.observable(false);
this.isActivated = ko.observable(false);
this.showCreationInformation = ko.observable(true);
this.wipeArtworkModel = new app.models.WipeArtworkDialogModel(datasource);
this.internalArtworkModel = ko.observable();
this.editCategoryModel = new app.models.EditDhCategoryModel();
// alert("data=" + this.artworkStatuses);
this.isReadOnly = ko.computed(function () {
if (!self.internalArtworkModel()) {
return true;
}
return self.internalArtworkModel().isReadOnly();
});
this.canSaveStudioStatusOnly = ko.computed(function () {
if (!self.internalArtworkModel()) {
return false;
}
return self.internalArtworkModel().isUnblockStatusActive()
&& self.internalArtworkModel().isReadOnly()
&& self.internalArtworkModel().isStudioStatusVisible();
});
this.canSaveCatRaisOnly = ko.computed(function () {
if (!self.internalArtworkModel()) {
return false;
}
return self.internalArtworkModel().isReadOnly()
&& self.internalArtworkModel().isArchiveProvenanceWriter();
});
this.isNewOrDuplicating = ko.computed(function () {
return self.isNewArtwork() || duplicateArtwork();
});
this.activate = function (params) {
var id = params.id || 0;
self.isNewArtwork(!id);
return self.loadArtworkDetails(id);
};
this.isValid = function () {
return self.internalArtworkModel() && self.internalArtworkModel().errors().length == 0;
};
this.loadArtworkDetails = function (id) {
var action = self.isNewArtwork() ? datasource.artworks.getEmpty : datasource.artworks.getById;
return action(id)
.done(function (result) {
var mapper = app.mappers["fabrication"],
model = mapper.map(result.data);
self.internalArtworkModel(model);
self.editCategoryModel.categoryId(model.defaultDhCategoryId);
self.editCategoryModel.setModeAsCategoryChanging();
if (self.isNewArtwork()) {
self.editCategoryModel.setModeAsCreation();
self.editCategoryModel.show();
}
self.isActivated(true);
duplicateArtwork(false);
$("a#science-link").attr("href", window.location.href);
triggerLoadEvent();
});
};
this.saveArtwork = function () {
if (self.isValid()) {
return saveInternal($.noop);
}
};
this.editCategory = function () {
self.editCategoryModel.show();
};
this.onLoad = function (callback) {
emitter.subscribe("load", callback);
};
this.onCategoryChanged = function (callback) {
emitter.subscribe("category-changed", callback);
};
this.onDelete = function (callback) {
emitter.subscribe("delete", callback);
};
this.onCreated = function (callback) {
emitter.subscribe("artwork-created", callback);
};
this.onUpdated = function (callback) {
emitter.subscribe("artwork-updated", callback);
};
this.onBlanked = function (callback) {
emitter.subscribe("blanked", callback);
};
this.saveStudioStatusInt = function () {
return saveStudioStatusInternal($.noop);
};
this.saveCatRaisInt = function () {
return saveCatRaisInternal($.noop);
};
this.saveStudioStatus = ko.asyncCommand({
execute: function (complete) {
saveStudioStatusInternal(complete);
},
canExecute: function (isExecuting) {
return !isExecuting && self.canSaveStudioStatusOnly();
}
});
this.saveCatRais = ko.asyncCommand({
execute: function (complete) {
saveCatRaisInternal(complete);
},
canExecute: function (isExecuting) {
return !isExecuting && self.canSaveCatRaisOnly();
}
});
this.save = ko.asyncCommand({
execute: function (complete) {
return saveInternal(complete);
},
canExecute: function (isExecuting) {
if (!self.isValid()) {
showErrors();
}
return !isExecuting && self.isValid();
}
});
function saveStudioStatusInternal(completeCallback) {
var model = self.internalArtworkModel();
triggerSaveStudioStatusEvent();
datasource.artworks.saveStudioStatus(model.artworkId(), model.artworkStatusId(), model.studioStatusId).always(completeCallback);
}
function saveCatRaisInternal(completeCallback) {
var model = self.internalArtworkModel();
triggerSaveStudioStatusEvent();
datasource.artworks.saveCatRais(model.artworkId(), model.inclusion(), model.image(), model.details(), model.crReady(), model.selectedVolume.selected.value).always(completeCallback);
}
this.onSaveStudioStatus = function (callback) {
emitter.subscribe("saveStudioStatus", callback);
};
function triggerSaveStudioStatusEvent() {
emitter.publish("saveStudioStatus");
}
this.beginArtworkDeletion = function () {
if (!self.internalArtworkModel().canBeBlanked) {
jAlert(resources.unableToDeleteArtworkMessage, resources.deleteArtworkTitle);
return;
}
self.wipeArtworkModel.beginArtworkDeletion(self.internalArtworkModel().artworkId());
};
this.beginArtworkBlanking = function () {
if (!self.internalArtworkModel().canBeBlanked) {
//jAlert(resources.unableToBlankArtworkMessage, resources.blankArtworkTitle);
app.alerts.appConfirm(resources.unableToBlankArtworkFullyMessage, resources.blankArtworkTitle, function (ok) {
if (ok) {
self.wipeArtworkModel.beginArtworkBlanking(self.internalArtworkModel().artworkId());
}
});
return;
}
self.wipeArtworkModel.beginArtworkBlanking(self.internalArtworkModel().artworkId());
};
this.duplicateArtwork = function () {
if (!self.internalArtworkModel()) {
return;
}
duplicateArtwork(true);
resetModelBeforeDuplication();
if (self.isValid()) {
showErrors();
}
jAlert(resources.duplicateMessage, resources.duplicateTitle);
};
function saveInternal(completeCallback) {
var mapper = app.mappers["fabrication"];
//alert("saving now...");
var unmappedModel = mapper.toJS(self.internalArtworkModel());
if (duplicateArtwork()) {
return saveDuplicatedArtwork(unmappedModel, completeCallback);
}
if (unmappedModel.artworkId) {
return updateArtwork(unmappedModel, completeCallback);
}
return createArtwork(unmappedModel, completeCallback);
}
function saveDuplicatedArtwork(model, completeCallback) {
return datasource.artworks.duplicate(model)
.always(completeCallback)
.fail(function (response) {
jAlert(resources.saveError);
})
.done(function (id) {
triggerArtworkCreatedEvent(id);
duplicateArtwork(false);
self.showCreationInformation(true);
});
}
function updateArtwork(model, completeCallback) {
return datasource.artworks.save(model)
.always(completeCallback)
.fail(function (response) {
jAlert(resources.saveError);
})
.done(function (result) {
if (!result) {
return;
}
var mapper = app.mappers["fabrication"],
mappedModel = mapper.map(result.data);
triggerUpdateEvent(
{
dhCategory: mappedModel.dhCategory(),
refNumber: mappedModel.refNumber(),
computedTitle: mappedModel.computedTitle()
});
self.internalArtworkModel(mappedModel);
});
}
function createArtwork(model, completeCallback) {
return datasource.artworks.save(model)
.always(completeCallback)
.fail(function (response) {
jAlert(resources.saveError);
})
.done(function (id) {
if (id) {
triggerArtworkCreatedEvent(id);
}
});
}
function triggerLoadEvent() {
var model = self.internalArtworkModel();
emitter.publish("load", {
artworkId: model.artworkId() || 0,
dhCategory: model.dhCategory() || "",
refNumber: model.refNumber() || "",
computedTitle: model.computedTitle() || "",
isForSale: model.isForSale
});
}
function triggerArtworkCreatedEvent(artworkId) {
emitter.publish("artwork-created", artworkId);
}
function triggerCategoryChangedEvent() {
emitter.publish("category-changed", {
name: self.internalArtworkModel().dhCategoryName(),
id: self.internalArtworkModel().dhCategoryId()
});
}
function triggerDeleteEvent(e, artworkId) {
emitter.publish("delete", artworkId);
}
function triggerUpdateEvent(data) {
var model = self.internalArtworkModel();
data.isCategoryChanged = model.initialCategoryId !== model.dhCategoryId();
emitter.publish("artwork-updated", data);
}
function showErrors() {
var model = self.internalArtworkModel();
if (model) {
model.errors.showAllMessages();
}
}
function resetModelBeforeDuplication() {
var model = self.internalArtworkModel();
model.refNumber("");
model.computedTitle("");
model.isOnDisplay(false);
model.isOnRestoration(false);
model.salesStatus("Not set");
model.creationDate("");
model.creatorName("");
model.isFramed(false);
model.framedDate("");
model.framedUserName("");
model.framedUserInitials("");
model.framedUserId(null);
model.locations(null);
model.isInsuranceComponent(false);
model.dateWebsiteWork("");
model.dateWebsiteReady("");
model.dateWebsiteUploaded("");
model.dateRemovedFromWebsite("");
model.collectionListing("");
model.salesStatusChangedDate("");
model.salesStatusChangerUserInitials("");
model.salesStatusChangerUserName("");
self.showCreationInformation(false);
}
function triggerBlanked(artworkId) {
emitter.publish("blanked", { artworkId: artworkId, flag: true });
}
(function () {
self.editCategoryModel.onChanged(function (e, categoryId) {
self.internalArtworkModel().dhCategoryId(categoryId);
triggerCategoryChangedEvent();
});
self.editCategoryModel.onCancel(function () {
if (self.isNewArtwork()) {
self.isNewArtwork(false);
app.router.back();
}
});
self.wipeArtworkModel.onDelete(triggerDeleteEvent);
self.wipeArtworkModel.onBlank(function (e, artworkId) {
self.loadArtworkDetails(artworkId);
triggerBlanked(artworkId);
});
})();
};
})(app);
It also uses a mapper:
(function (app, ko) {
app.namespace("mappers");
var s = app.urls.shared,
datasource = app.datasource;
var mapSettings = {
selectedMedium: {
create: function (options) {
return new ListModel({
isRemoteSource: true,
url: s.get('dictionaryUrl'),
listType: datasource.dictionaryTypes.get("medium"),
currentValue: options.data,
nobutton: true,
templateName: "textAreaTemplate"
});
}
},
artist: {
create: function (options) {
return new ListModel({
isRemoteSource: true,
url: s.get('dictionaryUrl'),
listType: datasource.dictionaryTypes.get("artist"),
currentValue: options.data,
onlyPreparedValues: false
});
}
},
manager: {
create: function (options) {
return new ListModel({
isRemoteSource: true,
url: s.get('dictionaryUrl'),
listType: datasource.dictionaryTypes.get("manager"),
currentValue: options.data,
onlyPreparedValues: false
});
}
},
managerStatus: {
create: function (options) {
return new ListModel({
isRemoteSource: true,
url: s.get('dictionaryUrl'),
listType: datasource.dictionaryTypes.get("managerStatus"),
currentValue: options.data,
onlyPreparedValues: false
});
}
},
selectedVolume: {
create: function (options) {
return new ListModel({
isRemoteSource: true,
url: s.get('dictionaryUrl'),
listType: datasource.dictionaryTypes.get("volume"),
currentValue: options.data,
onlyPreparedValues: false,
readonly: !options.parent.isArchiveProvenanceWriter(),
editable: options.parent.isArchiveProvenanceWriter()
});
}
},
salesStatus: {
create: function (options) {
var value = options.data || "Not set";
return ko.observable(value);
}
},
oldReferences: {
create: function (options) {
return ko.observable(options.data);
}
},
copy: ['defaultDhCategoryId', "canBeDeleted", "canBeBlanked", "isForSale"],
ignore: ["dimensions", "dimensionUnits", "selectedMediumTypes", "selectedSeries", "selectedSubSeries", "selectedAkaNames", "selectedWebSiteMediumTypes", 'selectedWebSiteCategoryIds', "locations", "hasInTransitLocations"]
},
unmapSettings = {
ignore: ["selectedMedium", "artist", "manager", "managerStatus", "mediumTypesModel", "dimensionModel", "seriesModel", "subSeriesModel", "akaModel", "webSiteMediumTypesModel", "webSiteCategoriesModel", "selectedVolume"]
},
map = function (json) {
var convertedObject = ko.utils.parseJson(json);
var model = ko.mapping.fromJS(convertedObject, mapSettings);
model.artworkStatusesModel = new ListModel({
isRemoteSource: false,
currentValue: convertedObject.selectedArtworkStatus,
data: convertedObject.artworkStatuses,
//data: filteredSalesStatuses,
onlyPreparedValues: true,
allowNull: false,
readonly: true
});
alert("mooo=" + model.artworkStatuses()[0].name());
alert("mooo=" + model.artworkStatuses()[1].name());
alert("mooo=" + model.artworkStatuses()[2].name());
//alert("mooo=" + model.artworkStatuses()[3].name());
//alert("mooo=" + model.artworkStatuses()[4].name());
model.dhCategoryName = ko.computed(function () {
var categories = model.dhCategories();
for (var i = 0; i < categories.length; i++) {
var category = categories[i];
if (category.id() == model.dhCategoryId()) {
return category.name();
}
}
return "";
});
model.initialCategoryId = convertedObject.dhCategoryId;
model.dimensionModel = new app.models.DimensionModel(convertedObject.dimensions);
ko.utils.arrayForEach(convertedObject.dimensionUnits, function (unit) {
model.dimensionModel.units.push(unit);
});
model.locations = ko.observable(convertedObject.locations ? {
archivedLocations: $.map(convertedObject.locations, function (elem) {
return elem.isArchived ? elem : null;
}),
activeLocations: $.map(convertedObject.locations, function (elem) {
return elem.isArchived ? null : elem;
}),
hasInTransitLocations: convertedObject.hasInTransitLocations
} : convertedObject.locations);
model.mediumTypesModel = new app.models.MultilistModel(convertedObject.selectedMediumTypes, { url: s.get("mediumTypesUrl") });
model.seriesModel = new app.models.MultilistModel(convertedObject.selectedSeries, { url: s.get("seriesUrl") });
model.subSeriesModel = new app.models.MultilistModel(convertedObject.selectedSubSeries, { url: s.get("subSeriesUrl") });
model.akaModel = new app.models.MultilistModel(convertedObject.selectedAkaNames, { url: s.get("akaUrl") });
model.webSiteMediumTypesModel = new app.models.MultilistModel(convertedObject.selectedWebSiteMediumTypes, { url: s.get("webSiteMediumTypeUrl") });
model.webSiteCategoriesModel = new app.models.MultilistModel(convertedObject.selectedWebSiteCategories, { url: s.get("webSiteCategoryUrl") });
model.isStudioStatusVisible = ko.computed(function () {
var foundStatuses = ko.utils.arrayFilter(model.artworkStatuses(), function (status) {
return status.id() == model.artworkStatusId();
});
var currentStatus = foundStatuses[0];
if (currentStatus) {
return currentStatus.name().toLowerCase() == "in progress";
}
return false;
});
addValidationOptions(model);
return model;
},
toJS = function (artwork) {
var result = ko.mapping.toJS(artwork, unmapSettings);
result.artist = artwork.artist.toJS();
result.manager = artwork.manager.toJS();
result.managerStatus = artwork.managerStatus.toJS();
result.selectedMedium = artwork.selectedMedium.toJS();
result.selectedVolume = artwork.selectedVolume.toJS();
result.dimensions = artwork.dimensionModel.toJS();
result.selectedMediumTypes = artwork.mediumTypesModel.toJS();
result.selectedSeries = artwork.seriesModel.toJS();
result.selectedSubSeries = artwork.subSeriesModel.toJS();
result.selectedAkaNames = artwork.akaModel.toJS();
result.selectedWebSiteMediumTypes = artwork.webSiteMediumTypesModel.toJS();
result.selectedWebSiteCategories = artwork.webSiteCategoriesModel.toJS();
return result;
};
function isValidationRequired(model) {
var artworkStatus = ko.utils.unwrapObservable(model.artworkStatusId);
var dhCategory = ko.utils.unwrapObservable(model.dhCategoryId);
return artworkStatus == app.global.dictionary.get("compiteArtworkStatus")
&& dhCategory != app.global.dictionary.get("ignoreDHCatigory");
}
function addValidationOptions(model) {
model.artworkStatusId.extend({ required: true });
model.title.extend({ required: true });
model.artist.selected.value.extend({ required: true });
model.startYear.extend({
lessEqualThan: {
params: model.endYear,
onlyIf: function () {
return (model.endYear() != undefined && model.endYear() != null);
},
message: 'From Date should be less or equal than a To Date'
}
});
model.endYear.extend({
required: {
params: true,
onlyIf: function () {
return isValidationRequired(model);
},
message: "To Date field is required"
}
});
model.selectedMedium.selected.value.extend({
required: {
params: true,
onlyIf: function () {
return isValidationRequired(model);
},
message: "Medium is required"
}
});
model.mediumTypesModel.hasValue.extend({
equal: {
params: true,
onlyIf: function () {
return isValidationRequired(model);
},
message: "Medium Type is required"
}
});
model.akaModel.hasValue.extend({
equal: {
params: true,
onlyIf: function () {
return isValidationRequired(model);
},
message: "Aka is required"
}
});
model.isInsuranceComponent.extend({
equal: {
params: true,
onlyIf: function () {
return isValidationRequired(model);
},
message: "At least 1 Insurance component should be chosen"
}
});
model.dimensionModel.hasDimensions.extend({
equal: {
params: true,
onlyIf: function () {
return isValidationRequired(model);
},
message: "The Dimension fields should be filled"
}
});
model.errors = ko.validation.group(model, { deep: true });
}
app.mappers.fabrication = {
settings: mapSettings,
map: map,
toJS: toJS
};
})(app, ko);
Where I have the alerts, the items are in the right order, so somewhere they are getting sorted but cant find out where?!
I hope someone can help?
It would be really appreciated.
Many thanks,
David.

Marionette CompositeView children.findByIndex not working as expected after collection sync

I have the following code that happens after a collection sync:
adGeneration: function() {
var child = this.children.findByIndex(this.children.length - 1);
console.log(child.model.toJSON());
eventer.trigger('generate:new:ad', child);
},
The problem I am running into, is that, after the first sync, the child element is a blank model:
First Time:
Object {id: "5-vp39kv3uiigxecdi", size: 26, price: "9.84", face: "( ⚆ _ ⚆ )"}
Every time after:
Object {length: 40, models: Array[41], _byId: Object, _listeningTo: Object, _listenId: "l14"…}
ProductsCollection
define(["backbone", "lodash", "fonts/products/model", "eventer"],
function(Backbone, _, ProductModel, eventer) {
'use strict';
var ProductsCollection = Backbone.Collection.extend({
model: ProductModel,
sort_key: 'price',
url: '/api/products',
offset: 0,
initialize: function() {
this.listenTo(eventer, 'fetch:more:products', this.loadMore, this);
},
comparator: function(item) {
return item.get(this.sort_key);
},
sortByKey: function(field) {
this.sort_key = field;
this.sort();
},
parse: function(data) {
return _.chain(data)
.filter(function(item) {
if(item.id) {
return item;
}
})
.map(function(item){
item.price = this.formatCurrency(item.price);
return item;
}.bind(this))
.value();
},
formatCurrency: function(total) {
return (total/100).toFixed(2);
},
loadMore: function() {
this.offset += 1;
this.fetch({
data: {
limit: 20,
skip: this.offset
},
remove: false,
success: function(collection) {
this.add(collection);
}.bind(this)
});
}
});
return ProductsCollection;
});
LayoutView that contains the View for the productions collection. The collection is fetched onShow of the layoutview
define(["marionette", "lodash", "text!fonts/template.html",
"fonts/controls/view", "fonts/products/view", "fonts/products/collection", "eventer"],
function(Marionette, _, templateHTML, ControlsView, ProductsView,
ProductsCollection, eventer) {
'use strict';
var FontsView = Marionette.LayoutView.extend({
regions: {
controls: '#controls',
products: '#products-list'
},
template: _.template(templateHTML),
initialize: function() {
this._controlsView = new ControlsView();
this._productsView = new ProductsView({
collection: new ProductsCollection({
reorderOnSort: false,
sort: false
})
});
this.listenTo(this._productsView.collection, 'sync', this.loading, this);
this.listenTo(eventer, 'fetch:more:products', this.loading, this);
this.listenTo(eventer, 'products:end', this.productsEnd, this);
},
onRender: function() {
this.getRegion('controls').show(this._controlsView);
this.getRegion('products').show(this._productsView);
this.loading();
},
onShow: function() {
this._productsView.collection.fetch({
data: {
limit: 20
}
})
},
productsEnd: function() {
this.loading();
this.$el.find('#loading').html("~ end of catalogue ~")
},
loading: function() {
var toggle = this.$el.find('#loading').is(':hidden');
this.$el.find('#loading').toggle(toggle);
}
});
return FontsView;
});
AdsView:
define(["marionette", "lodash", "text!ads/template.html", "eventer"],
function(Marionette, _, templateHTML, eventer) {
'use strict';
var AdsView = Marionette.ItemView.extend({
template: _.template(templateHTML),
ui: {
ad: '.ad'
},
initialize: function() {
this.listenTo(eventer, 'generate:new:ad', this.generateNewAd, this);
},
onShow: function() {
// Set add image onShow
this.ui.ad.prop('src', '/ad/' + this.randomNumber());
},
generateNewAd: function(childView) {
var newAd = this.ui.ad.clone(),
element = childView.$el,
elementId = childView.model.get("id");
newAd.prop('src', '/ad/' + this.randomNumber());
$("#" + elementId).after(newAd);
},
randomNumber: function() {
return Math.floor(Math.random()*1000);
},
setUpAd: function() {
this.ui.ad.prop('src', '/ad/' + this.randomNumber());
}
});
return AdsView;
});
I think your problem is in the ProductsCollection.loadMore method. There, in the success callback to your fetch you do,
function(collection) { this.add(collection); }
What's happening behind the scenes is that before your success callback is invoked, Backbone will first run Collection.set() on your data. By default, inside set your data will be parsed into a array of models returned by ProductsCollection.parse and if any new models are found they will be add'ed to your existing collection (note that unless you pass { remove: false } to your fetch options, models in your collection which are not in your last fetch will be removed. See Collection.set)
So, what happens when you do the fetch in loadMore, which is called after the first fetch, Backbone will first add all the models from the server (that are returned from ProductsCollection.parse) and then invoke the success callback of your fetch, which, essentially does one last add. And what it's add'ing is the ProductsCollection instance. Not collection.models, an array of models, rather a Backbone object that has a property models that holds a raw array of models. Hence, the strange output:
Object {length: 40, models: Array[41], _byId: Object, _listeningTo: Object,
_listenId: "l14"…}
Simply remove that success callback (which is unnecessary) and the last child view of your ProductsView should be the view rendered from the last model returned.

How make nested http query in AngularJs?

Iam build single page app in AngularJs,and I need call Facebook UI dialog.And if user click 'ok',or 'cancel',successReport methos not call immediately.This method call after i click on any button in page,or link.Similar to internal queue
service.showStreamDialog=function (json) {
if (json.stream) {
var newCardSentId = json.cardSentId;
FB.ui(json.stream, function (resp) {
if (resp && resp.post_id) {
reportService.successReport(newCardSentId,newCardSentId,resp.post_id);
} else {
reportService.cancelReport(newCardSentId);
}
});
}
};
// in other file
var successReport=function(cardId,cardSentId,postId){
var defered = $q.defer();
$http.post(reportUrl,$.param({
cardId:cardId,
cardSentId:cardSentId,
postId:postId,
accessToken: ACCESS_TOKEN
}))
.success(function(data){
defered.resolve(data);})
.error(function(data){
defered.reject(data);
});
return defered.promise;
};
I found problem. It was in integration facebook api in my app.I add $rootScope.$apply call,
and all working as i expected
service.showStreamDialog = function (json) {
if (json.stream) {
var newCardSentId = json.cardSentId;
FB.ui(json.stream, function (resp) {
if (resp && resp.post_id) {
$rootScope.$apply(function () {
$rootScope.$broadcast('CARD_SENT_SUCCESS', {cardSentId: newCardSentId,post_id:resp.post_id});
});
} else {
$rootScope.$apply(function () {
$rootScope.$broadcast('CARD_SENT_CANCEL', {cardSentId: newCardSentId});
});
}
});
}
};

AngularJS - Strange behaviour of promises in connection with notify()

As I want to implement a chat in AngularJS, I want to use the promise/deferred principle. My ChatService looks like the following:
factory('ChatService', ['$q', '$resource', function($q, $resource) {
var Service = {};
var connected = false;
var connection;
var chatResource = $resource('/guitars/chat/:action', {action: '#action'}, {
requestChatroomId: {
params: {
action: 'requestChatroomId'
},
method: 'GET'
},
sendMessage: {
params: {
action: 'sendMessage'
},
method: 'POST'
}
});
Service.connect = function(cb) {
var deferred = $q.defer();
chatResource.requestChatroomId(function(data) {
connection = new WebSocket('ws://127.0.0.1:8888/realtime/' + data.chatroomId);
connection.onerror = function (error) {
deferred.reject('Error: ' + error);
};
connection.onmessage = function (e) {
cb.call(this, e.data);
deferred.notify(e.data);
};
connected = true;
});
return deferred.promise;
};
Service.sendMessage = function(msg) {
if(!connected) {
return;
}
chatResource.sendMessage({message: msg});
}
return Service;
}])
My controller using the ChatService is:
app.controller('ChatCtrl', ['$scope', 'ChatService', function($scope, ChatService) {
$scope.chat = {};
$scope.chat.conversation = [];
var $messages = ChatService.connect(function(message) {
$scope.$apply(function() {
// #1 THIS FIRES EVERY TIME
$scope.chat.conversation.push(message);
});
});
$messages.then(function(message) {
console.log('Finishes - should never occur!')
}, function(error) {
console.log('An error occurred!')
}, function(message) {
// #2 THIS FIRES ONLY IF THERE IS AN INTERACTION WITH THE ANGULAR MODEL
console.log(message);
});
$scope.sendMessage = function(event) {
ChatService.sendMessage($scope.chat.message);
$scope.chat.message = '';
};
}]);
If something is pushed from the server, callback #1 is called, but callback #2 wont be called until there is some interaction with the angular-model, i.e. start writing something in the input-Box. What is the reason for that behaviour?
Okay the reason was, that AngularJS was not aware of a change. So I injected the $rootScope to my ChatService:
factory('ChatService', ['$q', '$resource', '$rootScope', function($q, $resource, $rootScope) {
and in connection.onmessage I called $apply() on $rootScope:
connection.onmessage = function (e) {
deferred.notify(e.data);
$rootScope.$apply();
};

ember ajax calls with promisses access result in templates and controller

I'm making a call to the server to get some data via an ajax request and I use a promise. While my template automatically updates as soon as the data is returned, I'm unlucky accessing the data in the controller, as I don't know exactly when it's there.
To resolve I can make an additional ajax call in the controller to get the same data but that feels ugly. Is there a nicer way to know when to access the data in the controller? As a work around I tried to call the function that needs the data on the didInsertElement but that didn't solve it.
App.ActiveDataSet = Ember.Object.extend({
progress: 0
});
App.ActiveDataSet.reopenClass({
findAll: function(project_id) {
var result = [];
$.ajax({
url: '/active_data_sets.json',
type: 'GET',
data: {'project_id': project_id}
}).then(function(response) {
response.active_data_sets.forEach(function(newset) {
result.addObject(App.ActiveDataSet.create(newset));
});
});
return result;
}
});
App.MapviewShowRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('activedatasetIndex').set('content', App.ActiveDataSet.findAll(model.id));
}
});
App.MapviewShowController = Ember.ObjectController.extend({
needs: ['activedatasetIndex'],
content: null,
dataSets: [],
createDataSets: function() {
// create the datasets
for (var counter = 0; counter < this.get('controllers.activedatasetIndex.content').length; counter++) {
alert(dataSets[counter].ds.name);
}
}
});
App.MapviewShowView = Ember.View.extend({
map: null,
didInsertElement: function() {
var map = null;
var myOptions = {
zoom: 8,
center: new google.maps.LatLng(52.368892, 4.875183),
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
navigationControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(this.$('#map_canvas').get(0), myOptions);
this.set('map', map); //save for future updations
var h = $(window).height(),
offsetTop = 60; // Calculate the top offset
$('#map_canvas').css('height', (h - offsetTop));
// get the datasets
this.controller.createDataSets();
}
});
Have you tried continuing the promises chain:
App.ActiveDataSet.reopenClass({
findAll: function(project_id) {
return $.ajax({
url: '/active_data_sets.json',
type: 'GET',
data: {'project_id': project_id}
}).then(function(response) {
var result = [];
response.active_data_sets.forEach(function(newset) {
result.addObject(App.ActiveDataSet.create(newset));
});
return result;
});
}
});
App.MapviewShowRoute = Ember.Route.extend({
setupController: function(controller, model) {
App.ActiveDataSet.findAll(model.id).then(function(content) {
this.controllerFor('activedatasetIndex').set('content', content);
});
}
});

Resources