I have Control with configurable defaults parameters:
define([
'jquery',
'can',
'moment',
'controls/base',
'mustache!./becauseYouShopped_view',
'./becauseYouShopped_model',
'coreMods/clickHistory/v2/clickHistory_model',
'coreMods/addToFavorite/addToFavorite_control',
], function ($, can, moment, BaseControl, becauseYouShoppedView, BecauseYouShoppedModel, ClickHistoryModel, AddToFavorite) {
var startDate = moment().subtract(90, 'days').format('YYYY-MM-DD'),
endDate = moment().add(1, 'days').format('YYYY-MM-DD');
var BecauseYouShopped = BaseControl({
defaults: {
moduleConfigName: 'becauseYouShopped',
hideRebatePrefix: true,
rebateAdditionalPrefix: 'Shop and',
useOldRebateValueWithoutCurrency: true,
tiered: {
rebateAdditionalPrefix: 'Shop and earn',
useOldRebateValueWithoutCurrency: true
},
apiParams: {
start_date: startDate,
end_date: endDate,
},
headingPrefix: 'Because you shop at',
dataLimitForGetSimilarMerchant: 14,
}
}, {
init: function (element, options) {
ClickHistoryModel.findOne(options.apiParams).done(function (data) {
var memberClicksAndOrders = data.attr().response;
if (memberClicksAndOrders.length) {
this.shoppedMerchantsInfo = this.getRecentlyShoppedMerchantName(memberClicksAndOrders);
if (this.shoppedMerchantsInfo) {
this.getRecommendedMerchantsAndRender();
}
}
}.bind(this)).fail(function (error) {
mn.log(error);
});
},
return BecauseYouShopped;
});
And I need to export this parameter dataLimitForGetSimilarMerchant: 14, to the Model of this Control for use it in getSimilarMerchantData function.
But when I importing it - in way as You can see on the code here:
define(['jquery', 'can', 'utils/promiseUtils', 'models/base', 'controls/base', 'coreMods/bonusComponents/becauseYouShopped/becauseYouShopped_control',], function ($, can, promiseUtils, BaseModel, BaseControl, BecauseYouShopped) {
console.log("1 log: ", BecauseYouShopped);
return BaseModel.extend({
/**
* Get response from one content group and type
* #param contentGroupIdAndType
* #returns {*}
*/
getSimilarMerchantData: function (merchantId, merchantName) {
var deferred = $.Deferred(),
self = this;
var controlOptions = BecauseYouShopped.defaults.dataLimitForGetSimilarMerchant;
console.log("2 log: ", BecauseYouShopped);
if (!merchantId) {
return can.Deferred().reject('merchant id is not specified');
}
var options = {
url: mn.contentAPIEndPoint + '/merchants/' + merchantId + '/similar',
method: 'GET',
dataType: 'json',
headers: self.getAjaxHeaders(),
data: self.getDefaultAjaxData({limit: controlOptions})
};
}, {});
});
I received that BecauseYouShopped is undefined
So, how can I import this parameter to the model?
You have the BecauseYouShopped control importing the BecauseYouShopped model, but the model is also importing the control. This sort of circular dependency can sometimes be resolved by your dependency manager if you are not trying to use the dependency module in-thread -- StealJS does this, for example -- but if you are using RequireJS like it seems you are based on the use of define(), you will need to require() one of your dependencies late rather than define()'ing it on initial load. More on this at https://requirejs.org/docs/api.html#circular
Related
I'm just having a play with Vue.js (pretty new to javascript too) and trying to access the events in my Google calendar.
I keep getting 'undefined' when looking in the console.
new Vue({
el: '#app',
data: {
client_id: 'my_client_id',
scopes: ["https://www.googleapis.com/auth/calendar.readonly"],
events: {
title: 'Upcoming Events',
items: [],
}
},
created: function () {
this.loadCalendarApi();
},
methods: {
addEvent: function (event) {
this.events.items.push({
title: event.summary,
date: event.start.dateTime
});
},
loadCalendarApi: function () {
gapi.client.load('calendar', 'v3', this.listUpcomingEvents);
},
listUpcomingEvents: function () {
var request = gapi.client.calendar.events.list({
'calendarId': 'primary',
'timeMin': (new Date()).toISOString(),
'showDeleted': false,
'singleEvents': true,
'maxResults': 10,
'orderBy': 'startTime'
});
var events = this.requestEvents(request);
console.log(events);
},
requestEvents: function (request) {
return request.execute(function (resp) {
resp.items;
});
},
},
});
I think the offending code is somewhere in the requestEvents method.
I also know that 'this.addEvent' is not in scope to be able to refer to the Vue object from inside the request.execute function but I don't know what I need to change.
Can anyone help me or let me know what I'm doing wrong?
Thanks!
Two ways you could do it, one would be to change execute(function) to execute(function, vue) and pass this in as the second argument. Then you could access it like so:
//update your execute function to pass along the vue variable into the response, then
requestEvents: function (request) {
var events = request.execute(function (resp, vue) {
for (i = 0; i < resp.items.length; i++) {
vue.addEvent(resp.items[i]);
}
}, this);
return events;
}
Or if you have jQuery available, you can take a look at $.proxy() which alters a function to use the current context: https://api.jquery.com/jQuery.proxy/
requestEvents: function (request) {
var events = request.execute($.proxy(function (resp) {
for (i = 0; i < resp.items.length; i++) {
this.addEvent(resp.items[i]);
}
}, this));
return events;
}
This way the anonymous response function will be run in the context of your Vue object.
Edit: I found this page as well which shows how you can bind the current context to a function using native JS, jQuery, or Underscore. Any of these would work: https://jsperf.com/bind-vs-jquery-proxy/5
Nowadays you could use the request => {} notation instead of function(request) {} notation, which will pass on the context to the executing method so this.AddEvent() will work as expected.
I am having an issue with a method being overwritten on a dependency that is being injected with Require.
Currently I am have a utility that adds and controls some notifacations across our site that you can see below.
define([
'jQuery',
'Underscore',
'Backbone',
'Data',
'Window',
'text!utilities/notify/templates/utility.html'
], function($, _, Backbone, Data, Window, Template) {
var Notify = Backbone.View.extend({
events: {
'click': 'close'
},
initialize: function() {
var self = this;
_.bindAll(this);
// add notify if not in the dom
var element = $('#notify')[0];
if(_.isEmpty(element)) {
var template = _.template(Template, {});
$('body').prepend(template);
}
Data.add([
// notify object
{
id: 'notify',
addToUrl: false,
addToHistory: false
}
]);
}
return new Notify;
});
(This is only a small portion of this file with the relevant data)
The Data dependency is a wrapper to add a few helper methods to deal with Collections. But we do not overload or modify the add method on the Collection in anyway. The problem I am facing is that in every modern evergreen browser (chrome, firefox, etc) Data is injected correctly and Data.add() works as expected. But in IE8 (sadly I have to support this) the Data.add method sudenly executes a function in Googles Adsense async-ads.js file that we use on our page. When this happens its causes a crazy recursion and IE8 gives a stack overflow message.
I am totally perplexed as to how the Data.add() method is being overwritten by a 3rd party JS function! Any ideas would be greatly appriciated!
Included JS Version Info
Backbone 1.0.0
Require 2.1.2
Underscore 1.3.3
EDIT: I've included the code from the Data utility as requested
/**
#appular {utility} data - designed to store variables for apps that multiple modules may need access too. works closely with the router to keep the url updated as well.
#extends backbone.collection
#define Data
*/
define([
'jQuery',
'Underscore',
'Backbone',
'utilities/data/models/data',
'Cookies'
], function($, _, Backbone, DataModel, Cookies){
var Collection = Backbone.Collection.extend({
model: DataModel,
lastChanged: '',
initialize: function() {
_.bindAll(this);
this.on('add', function(model) {
model.on('change', function() {
this.lastChanged = model.get('id');
this.trigger('dataChanged', model.get('id'));
}, this);
}, this);
},
// Sets data based on url data on initial load (ignores any parameters that are not defined in initialize above)
load: function(data) {
var dataInitialized = _.after(data.length, this.finalizeLoad);
_.each(data, function(dataArray) {
var model = this.get(dataArray[0]);
if(!model) {
model = _.find(this.models, function(model) { return model.get('alias').toLowerCase() === dataArray[0].toLowerCase(); });
}
if(model) {
model.set({value: decodeURIComponent(dataArray[1])}, {silent: true});
}
dataInitialized();
}, this);
},
finalizeLoad: function() {
var triggerInitialized = _.after(this.length, this.triggerInitialized);
_.each(this.models, function(model) {
if(model.get('getFromCookie')) {
var cookieName = null;
if(model.get('alias') !== '') {
cookieName = model.get('alias');
} else {
cookieName = model.get('id');
}
model.set({value: Cookies.get(cookieName)});
}
if(model.get('isArray') && _.isString(model.get('value'))) {
var value = model.get('value');
model.set('value', value.split(','));
}
triggerInitialized();
}, this);
},
/**
#doc {event} initialized - fires when all data has been loaded
*/
triggerInitialized: function() {
this.trigger('initialized');
},
/**
#doc {function} getValueOf - shortcut to get model's value
*/
getValueOf: function(name) {
return this.get(name).get('value');
},
/**
#doc {function} setValueOf - shortcut to set model's value
*/
setValueOf: function(name, value) {
return this.get(name).set('value', value);
}
});
return new Collection;
});
Edit 11/16/14: Version Information
DEBUG: Ember : 1.7.0 ember-1.7.0.js:14463
DEBUG: Ember Data : 1.0.0-beta.10+canary.30d6bf849b ember-1.7.0.js:14463
DEBUG: Handlebars : 1.1.2 ember-1.7.0.js:14463
DEBUG: jQuery : 1.10.2
I'm beating my head against a wall trying to do something that I think should be fairly straightforward with ember and ember-data, but I haven't had any luck so far.
Essentially, I want to use server data to populate a <select> dropdown menu. When the form is submitted, a model should be created based on the data the user chooses to select. The model is then saved with ember data and forwarded to the server with the following format:
{
"File": {
"fileName":"the_name.txt",
"filePath":"/the/path",
"typeId": 13,
"versionId": 2
}
}
The problem is, the typeId and versionId are left out when the model relationship is defined as async like so:
App.File = DS.Model.extend({
type: DS.belongsTo('type', {async: true}),
version: DS.belongsTo('version', {async: true}),
fileName: DS.attr('string'),
filePath: DS.attr('string')
});
The part that is confusing me, and probably where my mistakes lie, is the controller:
App.FilesNewController = Ember.ObjectController.extend({
needs: ['files'],
uploadError: false,
// These properties will be given by the binding in the view to the
//<select> inputs.
selectedType: null,
selectedVersion: null,
files: Ember.computed.alias('controllers.files'),
actions: {
createFile: function() {
this.createFileHelper();
}
},
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path'
});
var gotDependencies = function(values) {
//////////////////////////////////////
// This only works when async: false
file.set('type', values[0])
.set('version', values[1]);
//////////////////////////////////////
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this);
Ember.RSVP.all([
selectedType,
selectedVersion
]).then(gotDependencies);
}
});
When async is set to false, ember handles createRecord().save() POST requests correctly.
When async is true, ember handles GET requests perfectly with multiple requests, but does NOT add the belongsTo relationships to the file JSON during createRecord().save(). Only the basic properties are serialized:
{"File":{"fileName":"the_name.txt","filePath":"/the/path"}}
I realize this question has been asked before but I have not found a satisfactory answer thus far and I have not found anything that suits my needs. So, how do I get the belongsTo relationship to serialize properly?
Just to be sure that everything is here, I will add the custom serialization I have so far:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeIntoHash: function(data, type, record, options) {
var root = Ember.String.capitalize(type.typeKey);
data[root] = this.serialize(record, options);
},
keyForRelationship: function(key, type){
if (type === 'belongsTo') {
key += "Id";
}
if (type === 'hasMany') {
key += "Ids";
}
return key;
}
});
App.FileSerializer = App.ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
type: { serialize: 'id' },
version: { serialize: 'id' }
}
});
And a select:
{{ view Ember.Select
contentBinding="controller.files.versions"
optionValuePath="content"
optionLabelPath="content.versionStr"
valueBinding="controller.selectedVersion"
id="selectVersion"
classNames="form-control"
prompt="-- Select Version --"}}
If necessary I will append the other routes and controllers (FilesRoute, FilesController, VersionsRoute, TypesRoute)
EDIT 11/16/14
I have a working solution (hack?) that I found based on information in two relevant threads:
1) How should async belongsTo relationships be serialized?
2) Does async belongsTo support related model assignment?
Essentially, all I had to do was move the Ember.RSVP.all() to after a get() on the properties:
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
file.set('type', values[0])
.set('version', values[1]);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this));
}
So I needed to get() the properties that were belongsTo relationships before I save the model. I don't know is whether this is a bug or not. Maybe someone with more knowledge about emberjs can help shed some light on that.
See the question for more details, but the generic answer that I worked for me when saving a model with a belongsTo relationship (and you specifically need that relationship to be serialized) is to call .get() on the properties and then save() them in then().
It boils down to this:
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
// belongsTo set() here
file.set('type', selectedType)
.set('version', selectedVersion);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
// Save inside then() after I call get() on promises
file.save().then(onSuccess, onFail);
}.bind(this));
In a previous project, where I was not using Angular, I setup a Kendo.DataSource that used an OData endpoint as follows:
var userDS = new kendo.data.DataSource({
type: "odata",
transport: {
read: {
url: "/api/Users?$filter=USERGROUPS/any(usergroup: usergroup/ID eq '" + groupData.ID + "')", // only need to expand users for the selected group
dataType: "json", // the default result type is JSONP, but WebAPI does not support JSONP
},
update: {
url: function (data) {
// TODO: write UpdateEntity controller method
return "/api/Users(" + groupData.ID + ")";
},
dataType: "json"
},
destroy: {
url: function (data) {
// TODO: write Delete controller method
return "/api/Users(" + groupData.ID + ")";
},
dataType: "json"
},
parameterMap: function (options, type) {
// this is optional - if we need to remove any parameters (due to partial OData support in WebAPI
var parameterMap = kendo.data.transports.odata.parameterMap(options);
return parameterMap;
}
},
Now, introducing AngularJS into the mix, I would like to know how to define the read, update and destroy events using my AngularJS factory, where there is no URL.
My factory contracts are setup as follows:
contentTypesFactory.getList()
contentTypesFactory.insert(contentType)
contentTypesFacotry.remove(id)
The first problem I see with .getList() is that it doesn't take in any query string parameters, like $orderby and $inlinecount=allpages which I need for use with the KendoUI Grid. It is inside this factory that the URL is defined, then calls an abstract factory (see below).
I need to somehow pass in the URL and the entity name to my factory from the kendo.datasource url: function (remember, that the grid control will append whatever OData querystring parameters are required).
So, how I would setup the factory to output the data expected for each of the CRUD events.
Data source definition:
$scope.contentTypesDataSource = new kendo.data.HierarchicalDataSource({
type: "odata",
transport: {
read: {
//url: "/api/UserGroups?$orderby=GROUPNAME",
url: '/odata/ContentTypes',
//function (data) {
// pass in the URL to the abstract factory
//},
dataType: "json" // the default result type is JSONP, but WebAPI does not support JSONP
},
update: {
},
destroy: {
},
parameterMap: function (options, type) { ...
Abstract repository:
app.factory('abstractRepository', [function () {
// we will inject the $http service as repository service
// however we can later refactor this to use another service
function abstractRepository(repositoryService, whichEntity, odataUrlBase) {
//this.http = $http;
this.http = repositoryService;
this.whichEntity = whichEntity;
this.odataUrlBase = odataUrlBase;
this.route;
}
abstractRepository.prototype = {
getList: function () {
return this.http.get(this.odataUrlBase);
},
get: function (id) {
return this.http.get(this.odataUrlBase + '/' + id);
},
insert: function (entity) {
return this.http.post(this.odataUrlBase, entity);
},
update: function (entity) {
return this.http.put(this.odataUrlBase + '/' + entity.ID, this.whichEntity);
},
remove: function (id) {
return this.http.delete(this.odataUrlBase + '/' + id);
}
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
}
return abstractRepository;
}]);
ContentTypesFactory.js:
// each function returns a promise that can be wired up to callback functions by the caller
// the object returned from the factory is a singleton and can be reused by different controllers
app.factory('contentTypesRepository', ['$http', 'abstractRepository', function ($http, abstractRepository) {
var odataUrlBase = '/odata/ContentTypes'
var whichEntity = 'ContentTypes';
function contentTypesRepository() {
abstractRepository.call(this, $http, whichEntity, odataUrlBase);
}
abstractRepository.extend(contentTypesRepository);
return new contentTypesRepository();
}]);
After looking at kendo-examples-asp-net, I'm thinking that I should do away with the ContentTypesFactory and the abstract repository and call the OData endpoint directly - of course this is relatively easy.
However, my initial reason for creating an Angular repository was so that I could do JS unit testing on the data functions. To retain this feature, how can I call the abstract repository directly from the data source functions, and this the recommended way of accomplishing this?
I have some global parameters that I want to be sent in every time I call a fetch on a collection... my issue is I don't want to declare the data: { ... } every time I fetch.
Is there a way I can provide default parameters inside the Collection itself with the possibility to add more or override some?
For example:
Instead of doing this every time:
this.articlesCollection.fetch({
dataType: 'jsonp',
data: {
deviceType: GlobalVars.deviceType,
memberId: GlobalVars.memberId,
authToken: GlobalVars.authToken,
targetObjectId: userId,
limit: 50,
excludeArticleBodies: true,
excludeViewedItems: false
},
success: function() {
_this.render();
}
});
I'd like to just provide a one or two parameters and a success function, like this:
this.articlesCollection.fetch({
data: {
targetObjectId: userId
},
success: function() {
_this.render();
}
});
... and have the Collection look something like:
define([
'underscore',
'backbone',
'global',
'utilities',
'models/article/ArticleModel'
], function(_, Backbone, GlobalVars, Utils, ArticleModel){
var ArticlesCollection = Backbone.Collection.extend({
model: ArticleModel,
initialize : function(view) {
this.view = view;
},
dataType: 'jsonp',
data: {
deviceType: GlobalVars.deviceType,
memberId: GlobalVars.memberId,
authToken: GlobalVars.authToken,
limit: 50,
excludeArticleBodies: true,
excludeViewedItems: false
},
url : function() {
return GlobalVars.baseAPIUrl + '/API/GetArticles';
},
parse : function(data) {
return data.Articles;
}
});
return ArticlesCollection;
});
Here's a working jsFiddle with one approach: http://jsfiddle.net/LEuGq/1/
Basically, you configure both an object of defaultParams and params as properties of your collection, which are used to dynamically compute the correct URL when fetch() is called. This way is probably more in alignment with backbone than changing the API of fetch() to accept parameters, which it is not designed to do.
var ParamCollection = Backbone.Collection.extend({
defaultParams: {deviceType: 'raceCar', limit: 42},
params: {},
url: function() {
return "/paramcollection?" + $.param(_.defaults(this.params, this.defaultParams));
}
});
var paramCollection = new ParamCollection();
paramCollection.params.excludeArticleBodies = true;
paramCollection.params.limit = 52;
$("#debug").append(paramCollection.url());
Backbone uses jQuery's ajax call by default, so you can set up anything you need as a default using various methods. See this question for some examples: jQuery's ajaxSetup - I would like to add default data for GET requests only