I am having trouble with a directive. My model updates with data that is being loaded by a http call. Without the call the service the directive works correctly.
I have a directive called validationmessagefor that listens to when a field has been marked to have an error. When it has an error it displays the error message.
The plunker attached Plnkr
How do I get the directive to respond or load correctly against the model when using services to load data?
angular.module('dataApp', ['servicesModule', 'directivesModule'])
.controller('dataCtrl', ['$scope', 'ProcessService', 'ValidationRuleFactory', 'Validator',
function($scope, ValidationRuleFactory, Validator, ProcessService) {
$scope.viewModel = {};
var FormFields = {};
// we would get this from the meta api
ProcessService.getProcessMetaData().then(function(data) {
FormFields = {
Name: "Course",
Fields: [{
type: "text",
Name: "name",
label: "Name",
data: "",
required: true,
ruleSet: "personFirstNameRules"
}, {
type: "text",
Name: "description",
label: "Description",
data: "",
required: true,
ruleSet: "personEmailRules"
}]
};
$scope.viewModel = FormFields;
ProcessService.getProcessRuleData().then(function(data) {
var genericErrorMessages = {
required: 'Required',
minlength: 'value length must be at least %s characters',
maxlength: 'value length must be less than %s characters'
};
var rules = new ValidationRuleFactory(genericErrorMessages);
$scope.viewModel.validationRules = {
personFirstNameRules: [rules.isRequired(), rules.minLength(3)],
personEmailRules: [rules.isRequired(), rules.minLength(3), rules.maxLength(7)]
};
$scope.viewModel.validator = new Validator($scope.viewModel.validationRules);
});
});
var getRuleSetValuesMap = function() {
return {
personFirstNameRules: $scope.viewModel.Fields[0].data,
personEmailRules: $scope.viewModel.Fields[1].data
};
};
$scope.save = function() {
$scope.viewModel.validator.validateAllRules(getRuleSetValuesMap());
if ($scope.viewModel.validator.hasErrors()) {
$scope.viewModel.validator.triggerValidationChanged();
return;
} else {
alert('person saved in!');
}
};
}
]);
The directive to display the custom error message
(function(angular, $) {
angular.module('directivesModule')
.directive('validationMessageFor', [function() {
return {
restrict: 'A',
scope: {eID: '#val'},
link: function(scope, element, attributes) {
//var errorElementId = attributes.validationMessageFor;
attributes.$observe('validationMessageFor', function(value) {
errorElementId = value;
if (!errorElementId) {
return;
}
var areCustomErrorsWatched = false;
var watchRuleChange = function(validationInfo, rule) {
scope.$watch(function() {
return validationInfo.validator.ruleSetHasErrors(validationInfo.ruleSetName, rule.errorCode);
}, showErrorInfoIfNeeded);
};
var watchCustomErrors = function(validationInfo) {
if (!areCustomErrorsWatched && validationInfo && validationInfo.validator) {
areCustomErrorsWatched = true;
var validator = validationInfo.validator;
var rules = validator.validationRules[validationInfo.ruleSetName];
for (var i = 0; i < rules.length; i++) {
watchRuleChange(validationInfo, rules[i]);
}
}
};
// get element for which we are showing error information by id
var errorElement = $("#" + errorElementId);
var errorElementController = angular.element(errorElement).controller('ngModel');
var validatorsController = angular.element(errorElement).controller('validator');
var getValidationInfo = function() {
return validatorsController && validatorsController.validationInfoIsDefined() ? validatorsController.validationInfo : null;
};
var validationChanged = false;
var subscribeToValidationChanged = function() {
if (validatorsController.validationInfoIsDefined()) {
validatorsController.validationInfo.validator.watchValidationChanged(function() {
validationChanged = true;
showErrorInfoIfNeeded();
});
// setup a watch on rule errors if it's not already set
watchCustomErrors(validatorsController.validationInfo);
}
};
var getErrorMessage = function(value) {
var validationInfo = getValidationInfo();
if (!validationInfo) {
return '';
}
var errorMessage = "";
var errors = validationInfo.validator.errors[validationInfo.ruleSetName];
var rules = validationInfo.validator.validationRules[validationInfo.ruleSetName];
for (var errorCode in errors) {
if (errors[errorCode]) {
var errorCodeRule = _.findWhere(rules, {errorCode: errorCode});
if (errorCodeRule) {
errorMessage += errorCodeRule.validate(value).errorMessage;
break;
}
}
}
return errorMessage;
};
var showErrorInfoIfNeeded = function() {
var validationInfo = getValidationInfo();
if (!validationInfo) {
return;
}
var needsAttention = validatorsController.ruleSetHasErrors() && (errorElementController && errorElementController.$dirty || validationChanged);
if (needsAttention) {
// compose and show error message
var errorMessage = getErrorMessage(element.val());
// set and show error message
element.text(errorMessage);
element.show();
} else {
element.hide();
}
};
subscribeToValidationChanged();
if (errorElementController)
{
scope.$watch(function() {
return errorElementController.$dirty;
}, showErrorInfoIfNeeded);
}
scope.$watch(function() {
return validatorsController.validationInfoIsDefined();
}, subscribeToValidationChanged());
});
}
};
}]);
})(angular, $);
Related
Service
angular.module('App').service('producerContactSvc', ['$http', 'configSvc', function($http, configSvc) {
//Reset headers to avoid OPTIONS request (aka preflight)
$http.defaults.headers.common = {};
var _this = this;
_this.getProducerContacts = function(producerId) {
var endpoint = configSvc.backendUrl + '/producerContact/read?producerId=' + producerId;
return $http.get(endpoint)
.then(function(response) {
return response.data;
})
.catch(function(response) {
return response.data;
});
};
_this.addProducerContact = function(producerContact) {
var endpoint = configSvc.backendUrl + '/producerContact/create';
return $http.post(endpoint, producerContact)
.success(function(response) {
return response.data;
})
.catch(function(response) {
return response.data;
});
};
_this.deleteProducerContact = function(producerContactId, producerId) {
var endpoint = configSvc.backendUrl + '/producerContact/delete?producerContactId=' + producerContactId + '&' + 'producerId=' + producerId;
return $http.delete(endpoint)
.then(function(response) {
return response.data;
})
.catch(function(response) {
return response.data;
});
};
_this.updateProducerContact = function(producerContact) {
var endpoint = configSvc.backendUrl + '/producerContact/update';
return $http.put(endpoint, producerContact)
.then(function(response) {
return response.data;
})
.catch(function(response) {
return response.data;
});
};
}]);
Controller
angular.module('App').controller('producerAdminCtrl', ['$scope', '$rootScope', 'producerSvc', '$mdDialog', 'producerContactSvc', 'errorToastSvc', function($scope, $rootScope, producerSvc, $mdDialog, producerContactSvc, errorToastSvc) {
$scope.results = false;
$scope.producerTypeCodes = ['CX', 'FG'];
$scope.producerId = null;
$scope.documents = [];
$scope.selectedDocs = [];
$scope.producerContactRows = [];
$scope.missingName = false;
$scope.producerContactTypeCodes = ['MC', 'BC'];
/**
* Get a list of all producers on load
*/
producerSvc.getAllProducers()
.then(function(response) {
if (response !== null) {
$scope.unusedProducers = response.data;
} else {
$scope.noFound = true;
$scope.results = false;
$scope.message = 'No Data Received';
}
});
/**
* Perform a search on a producer. This will call the backend and find information about the producer searched for
*/
$scope.search = function() {
$scope.documents = [];
$scope.selectedDocs = [];
$scope.query = $scope.selectedItem.producerId;
producerSvc.getProducer($scope.query).then(function(response) {
if (response && response.data) {
$scope.producer = response.data;
$scope.producerId = $scope.query;
$scope.producer.inactiveDate = response.data.inactiveDate === null ? null : new Date(response.data.inactiveDate);
producerSvc.getAllDocs().then(function(response) {
_.forEach(response, function(doc) {
var docObject = {
documentId: doc.documentId,
documentDescription: doc.documentDescription,
documentName: doc.documentName,
selected: false
};
$scope.documents.push(docObject);
});
producerSvc.getProducerDocs($scope.producerId).then(function(response2) {
_.forEach(response2.data, function(docProducer) {
var docId = docProducer.documentId;
_.forEach($scope.documents, function(doc) {
if (docId === doc.documentId) {
doc.selected = true;
$scope.selectedDocs.push(doc);
}
});
});
$scope.documents.sort(function(x, y) {
return (x.selected === y.selected) ? 0 : x.selected ? -1 : 1;
});
$scope.results = true;
$scope.noFound = false;
});
});
} else {
$scope.noFound = true;
$scope.results = false;
$scope.message = 'No Producer Found';
}
});
producerContactSvc.getProducerContacts($scope.query).then(function(response) {
_.forEach(response, function(producerContact) {
var producerContactObject = {
producerContactName: producerContact.producerContactName,
editable: false,
producerContactEmail: producerContact.producerContactEmail,
producerContactType: producerContact.producerContactType,
invalid: false,
producerContactIsNew: false,
producerContactId: producerContact.producerContactId,
producerId: producerContact.producerId
};
$scope.producerContactRows.push(producerContactObject);
});
});
/**
* Add a new producerContact to the producerContactRows array.
*/
$scope.addProducerContact = function() {
var producerId = '';
var producerContactName = '';
var producerContactEmail = '';
var producerContactType = '';
var producerContactObject = {
producerId: producerId,
producerContactName: producerContactName,
editable: true,
producerContactEmail: producerContactEmail,
producerContactType: producerContactType,
invalid: false,
producerContactIsNew: true
};
$scope.producerContactRows.push(producerContactObject);
};
/**
* Enable edit mode on a selected producerContact.
* #param {number} producerContactIndex the index within the producerContactRows array of the producerContact to edit.
*/
$scope.editProducerContact = function(producerContactIndex) {
$scope.producerContactRows[producerContactIndex].editable = true;
};
/**
* Save a selected producerContact.
* #param {number} producerContactIndex the index within the producerContactRows array of the producerContact to save.
*/
$scope.saveProducerContact = function(producerContactIndex) {
if ($scope.producerContactRows[producerContactIndex].producerContactIsNew) {
producerContactSvc.addProducerContact($scope.sanitizeProducerContact($scope.producerContactRows[producerContactIndex])).then(function(response) {
$scope.producerContactRows[producerContactIndex].producerContactId = response.data.producerContactId;
});
$scope.producerContactRows[producerContactIndex].producerContactIsNew = false;
} else {
producerContactSvc.updateProducerContact($scope.sanitizeProducerContact($scope.producerContactRows[producerContactIndex]));
}
$scope.producerContactRows[producerContactIndex].editable = false;
};
/**
* Prompt the user for confirmation they want to delete a producerContact.
* #param {number} producerContactIndex the index within the producerContactRows array of the producerContact to delete.
*/
$scope.deleteProducerContact = function(producerContactIndex, ev) {
var deleteProducerContact = $mdDialog.confirm()
.title('Are you sure you want to delete this ProducerContact?')
.ariaLabel('Delete ProducerContact')
.targetEvent(ev)
.ok('Delete ProducerContact')
.cancel('Cancel');
$mdDialog.show(deleteProducerContact).then(function() {
$scope.performDelete(producerContactIndex);
});
};
/**
* Delete a selected producerContact after verification.
* #param {number} producerContactIndex the index within the producerContactRows array of the producerContact to delete.
*/
$scope.performDelete = function(producerContactIndex) {
console.log($scope.producerContactRows[producerContactIndex]);
if (!$scope.producerContactRows[producerContactIndex].producerContactIsNew) {
producerContactSvc.deleteProducerContact($scope.producerContactRows[producerContactIndex].producerContactId, $scope.producerId);
}
$scope.producerContactRows.splice(producerContactIndex, 1);
};
/**
* Save the producer information to the backend
*/
$scope.saveProducer = function() {
var docObjects = [];
var timestamp = new Date().getTime();
var user = null;
if ($rootScope.apmHeader) {
user = $rootScope.apmHeader.userI;
}
_.forEach($scope.selectedDocs, function(doc) {
var docObject = {
documentid: doc.documentId,
producerId: $scope.producerId,
approvedby: user,
dateapproved: timestamp
};
docObjects.push(docObject);
});
$scope.producer.documentList = docObjects;
producerSvc.saveProducerInfo($scope.producer);
};
}]);
Test Class
describe('Test Producer Contact', function() {
//Add an initialize here:
var $scope;
var $rootScope;
var controller;
// deferred variables
var getProducerDefer;
var getProducerContactsDefer;
var addProducerContactDefer;
var updateProducerContactDefer;
var deleteProducerContactDefer;
var producerContactSvc;
var mockProducerContacts = [{
producerContactId: '11111',
producerId: '1111',
producerContactName: 'abcd',
producerContactEmail: 'abcd#abc.com',
producerContactType: 'cc',
editable: false,
invalid: false,
producerContactIsNew: false
},
{
producerContactId: '11112',
producerId: '1111',
producerContactName: 'efgh',
producerContactEmail: 'efgh#xyc.com',
producerContactType: 'primary',
editable: false,
invalid: false,
producerContactIsNew: false
},
{
producerContactId: '12345',
producerId: '1111',
producerContactName: 'asdh',
producerContactEmail: 'asdh#xyc.com',
producerContactType: 'cc',
editable: false,
invalid: false,
producerContactIsNew: false
}
];
beforeEach(module('FirstSaleApp'));
beforeEach(inject(function($controller, producerSvc, producerContactSvc, _$q_, _$rootScope_) {
$scope = _$rootScope_.$new();
$rootScope = _$rootScope_;
producerContactSvc.deleteProducerContact = function() {
return null;
};
// We use the $q service to create a mock instance of defer
getProducerDefer = _$q_.defer();
getProducerContactsDefer = _$q_.defer();
addProducerContactDefer = _$q_.defer();
updateProducerContactDefer = _$q_.defer();
deleteProducerContactDefer = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn(producerSvc, 'getAllProducers').and.returnValue(getProducerDefer.promise);
spyOn(producerContactSvc, 'getProducerContacts').and.returnValue(getProducerContactsDefer.promise);
spyOn(producerContactSvc, 'addProducerContact').and.returnValue(addProducerContactDefer.promise);
spyOn(producerContactSvc, 'updateProducerContact').and.returnValue(updateProducerContactDefer.promise);
spyOn(producerContactSvc, 'deleteProducerContact').and.returnValue(deleteProducerContactDefer.promise);
$controller('producerAdminCtrl', {
$scope: $scope,
producerSvc: producerSvc,
producerContactSvc: producerContactSvc
});
// getProducerDefer.resolve(mockProducers);
getProducerContactsDefer.resolve(mockProducerContacts);
// $scope.$digest();
$rootScope.$digest();
}));
it('controller exists', function() {
expect(controller).not.toBeNull();
});
it('should get the producerContacts from the producerContact service', function() {
// $scope.producerContactRows = $scope.getProducerContacts(1111);
// The length should match
expect($scope.producerContactRows.length).toBe(3); // <-- coming as 0
// The values should be the same too
// Mock 1
expect($scope.producerContactRows[0].producerContactName).toBe(mockProducerContacts[0].producerContactName);
expect($scope.producerContactRows[0].producerContactEmail).toBe(mockProducerContacts[0].producerContactEmail);
expect($scope.producerContactRows[0].producerContactType).toBe(mockProducerContacts[0].producerContactType);
expect($scope.producerContactRows[0].editable).toBeFalsy();
expect($scope.producerContactRows[0].producerContactIsNew).toBeFalsy();
// Mock 2
expect($scope.producerContactRows[1].producerContactName).toBe(mockProducerContacts[1].producerContactName);
expect($scope.producerContactRows[1].producerContactEmail).toBe(mockProducerContacts[1].producerContactEmail);
expect($scope.producerContactRows[1].producerContactType).toBe(mockProducerContacts[1].producerContactType);
expect($scope.producerContactRows[1].editable).toBeFalsy();
expect($scope.producerContactRows[1].producerContactIsNew).toBeFalsy();
// Mock 3
expect($scope.producerContactRows[2].producerContactName).toBe(mockProducerContacts[2].producerContactName);
expect($scope.producerContactRows[2].producerContactEmail).toBe(mockProducerContacts[2].producerContactEmail);
expect($scope.producerContactRows[2].producerContactType).toBe(mockProducerContacts[2].producerContactType);
expect($scope.producerContactRows[2].editable).toBeFalsy();
expect($scope.producerContactRows[2].producerContactIsNew).toBeFalsy();
});
});
I'm new to angularjs - I have started working on an app that uses karma and jasmine to test. I am trying to use deferred promise to stub a method call. However I don't see the data after I digest (or apply) the scope. Any help is appreciated on what I might be missing. Thanks
I am trying to move the code vm.canGoForward from my controller to a service to hide the implementation details.
BEFORE CODE CHANGE
This worked fine.
View:
<button ng-disabled="!vm.canGoForward()" class="btn btn-primary" name="next" type="button" ng-click="vm.gotoStep(vm.currentStep + 1)">
Controller:
var vm = this;
vm.currentStep = 1;
vm.steps = WizardService.getWizardSteps(vm.formData);
vm.canGoForward = function() {
var res = true,
i,
nextStateIndex = vm.currentStep + 1;
if (nextStateIndex > vm.steps.length) {
return false;
}
for (i = 1; res && i <= nextStateIndex; i++) {
res = (res && vm.steps[i-1].isReady());
}
return !!res;
};
Service
var wizardService = {
getWizardSteps: getWizardSteps
};
return wizardService;
function getWizardSteps(formData) {
var wizardSteps = [
{
step: 1,
name: 'Name',
template: 'views/wizard/step1.html',
isReady: function() { return true; }
},
{
step: 2,
name: 'Email',
template: 'views/wizard/step2.html',
isReady: function() { return formData.firstName && formData.lastName; }
},
{
step: 3,
name: 'Job Category',
template: 'views/wizard/step3.html',
isReady: function() { return formData.email; }
}
];
return wizardSteps;
}
AFTER CODE CHANGE
View
Remains the same
Controller
var vm = this;
vm.currentStep = 1;
vm.steps = WizardService.getWizardSteps(vm.formData);
vm.canGoForward = WizardService.canGoForward(vm.currentStep, vm.steps);
Service
var wizardService = {
getWizardSteps: getWizardSteps,
canGoForward: canGoForward
};
return wizardService;
function getWizardSteps(formData) {
var wizardSteps = [
{
step: 1,
name: 'Name',
template: 'views/wizard/step1.html',
isReady: function() { return true; }
},
{
step: 2,
name: 'Email',
template: 'views/wizard/step2.html',
isReady: function() { return formData.firstName && formData.lastName; }
},
{
step: 3,
name: 'Job Category',
template: 'views/wizard/step3.html',
isReady: function() { return formData.email; }
}
];
return wizardSteps;
}
function canGoForward(currentStep, steps) {
console.log(steps);
var res = true,
i,
nextStateIndex = currentStep + 1;
if (nextStateIndex > steps.length) {
return false;
}
for (i = 1; res && i <= nextStateIndex; i++) {
res = (res && steps[i-1].isReady());
}
return !!res;
}
I now get the following error: TypeError: v2.canGoForward is not a function. How can I resolve it?
In your second version, the following line will actually call WizardService.canGoForward on the spot, not assign it:
vm.canGoForward = WizardService.canGoForward(vm.currentStep, vm.steps);
What gets assigned is the return value of that call, which obviously is not a function, hence the error message when a call is attempted later.
If you want to assign the function, and ensure the arguments get passed when it is called later, then use bind:
vm.canGoForward = WizardService.canGoForward.bind(WizardService, vm.currentStep, vm.steps);
I have the following code with the JSFiddle for the same here:
var result = [
{"Id": 10004, "PageName": "club"},
{"Id": 10040, "PageName": "qaz"},
{"Id": 10059, "PageName": "jjjjjjj"}
];
$.each(result, function(i, item) {
alert(result[i].PageName);
});
In order to see all the results, I have to click Ok in alert window two times. How can I display the contents using one alert only?
//f1.js ---
var PatientReviewPage = (function () {
var cls = function () { }
var self = cls.prototype;
self.urlKey = "showmrn";
self.getData = function (mrn_) {
/*
if (isEmpty(mrn_)) { alert("Invalid mrn in getData()"); return false; }
// Lookup the AJAX web service URL
var url = regman.getWebServiceURL(self.urlKey);
if (isEmpty(url)) { alert("Invalid URL in getData()"); return false; }
var ajaxRequest = jQuery.ajax({
//beforeSend: TODO: show spinner!
data: {
registry_id: regman.registry.id,
mrn: mrn_
},
dataType: "json",
method: "GET",
url: url
})*/
//Some code related to ajax reques
this.done(function (data_, textStatus_, jqXHR_) {
// Validate the web service and retrieve the status.
if (typeof (data_) === "undefined" || data_ === null) {
alert("Invalid data returned from web service");
return false;
}
if (isEmpty(data_.webservice_status) || isEmpty(data_.webservice_status.status)) {
alert("Invalid web service status");
return false;
}
if (data_.webservice_status.status != "SUCCESS") {
alert(data_.webservice_status.message);
return false;
}
self.processdataDocuments(data_.data_document_list);
});
this.fail(function (jqXHR_, textStatus_, errorThrown_) {
alert("Error in getData(): " + errorThrown_);
return false;
});
};
// Initialize the page
self.initialize = function () {
var mrn = regman.selectedData["mrn"];
if (isEmpty(mrn)) {
alert("Invalid MRN provided by calling page");
return false;
}
self.getData(mrn);
};
self.processdataDocuments = function (collection_) {
if (isEmpty(collection_) || collection_.length < 1) {
// Populate the count and exit
jQuery("#nlpDocumentCount").html("(0)");
return true;
}
var source =
{
localdata: collection_,
datatype: "array"
};
var dataAdapter = new $.jqx.dataAdapter(source, {
loadComplete: function (data) { },
loadError: function (xhr, status, error) { }
});
$("#nlpDocumentPanel").jqxGrid(
{
source: dataAdapter,
width: '1000',
height: 150,
columns: [
{
text: 'Type', datafield: 'nc_type'
},
{
text: 'SubType', datafield: 'nc_subtype'
},
{
text: 'Concept', datafield: 'concept_text'
},
{
text: 'Date', datafield: 'nc_dos'
}
]
});
// For getting the contents of a row, I am using jqxgrid approach as mentioned in their doc here :
// http://www.jqwidgets.com/getting-the-clicked-grid-row/
$("#nlpDocumentPanel").on('rowclick', function (event) {
var row = event.args.rowindex;
var datarow = $("#nlpDocumentPanel").jqxGrid('getrowdata', row);
response = JSON.stringify(datarow, null, 10)
//alert(jsonStringify); // This alert displays the JSON data in a formatted manner
var obj = jQuery.parseJSON(response);
//alert("display Subtype "+obj.nc_subtype) // Works fine
self.mySubtype = obj.nc_subtype;
});
};
//I added this line for the demo to show 'f2' accessing this property from 'f1'. You should remove it if copying this code into your application
self.mySubtype = "Foo";
return cls;
})();
var f1 = new PatientReviewPage();
//f2.js ---
var DocumentDetailsDialog = (function () {
var cls = function () { }
var self = cls.prototype;
alert(f1.mySubtype);
/*this.getData = function (IDNumber_) {
// some code will be here
var ajaxRequest = jQuery.ajax({
// some code will be here
})
.done(function (data_, textStatus_, jqXHR_) {
// some code will be here
})
.fail(function (jqXHR_, textStatus_, errorThrown_) {
// some code will be here
});
};*/
return cls;
})();
var f2 = new DocumentDetailsDialog();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You can use map() and join() and return string with page names.
var result = [
{"Id": 10004, "PageName": "club"},
{"Id": 10040, "PageName": "qaz"},
{"Id": 10059, "PageName": "jjjjjjj"}
];
var r = result.map(function(e) {
return e.PageName;
}).join(' ');
alert(r);
Build a string with the results and alert the string:
alert(result.map( _ => _.PageName).join(', '))
I've been working at making this script more modularized toady, and I am running into an error. Am I ordering my logic incorrectly?
Error Msg
Uncaught ReferenceError: accountId is not defined
Related Script Line
_.each(_.omit(accounts, _.keys(result.data.accounts)), function (account) {
accounts[accountId].push([result.timestamp.valueOf(), 0]);
});
Script
var debug = 0;
function paramsStr(p) {
var first = true;
var s = '';
_.each(p,function (value,key) {
if (!value) {
return;
}
if (first) {
s += '?';
first = false;
} else {
s += '&';
}
s += key+'='+value;
});
return s;
};
function parseReport(data)
{
var split = data.split('\n');
var results = [];
_.each(split,function (row) {
if (!row || row == "") {
return;
}
var myRow = JSON.parse(row);
myRow.timestamp = moment.utc(myRow.timestamp);
results.push(myRow);
});
return results;
}
function loadReport(report,granularity,startTime,endTime) {
if (debug && _.has(mock.reports,report) && _.has(mock.reports[report],granularity)) {
return jQuery.Deferred().resolve(parseReport(mock.reports[report][granularity])).promise();
}
var params = {
granularity : granularity
};
if (startTime) {
params['start-time'] = startTime.toISOString();
}
if (endTime) {
params['end-time'] = endTime.toISOString();
}
var url = '//analytics.locuslabs.com/api/reports/'+report+paramsStr(params);
return $.ajax({
url : url,
dataType : 'text'
}).then(function (data) {
return parseReport(data);
});
};
$(document).ready(function () {
var placeholder = $(".graph");
var oneMonth = moment().utc().subtract(1, 'months');
var twoWeeks = moment().utc().subtract(2, 'weeks');
//Function call to get the current time
function dateNow(target,timeFormat){
$(placeholder).siblings(target).text(moment(Date.now()).format(timeFormat));
}
function pollData(apiRoot,increment,startDate,label){
loadReport(apiRoot,increment, startDate).then(function (results) {
var totals = [];
var accounts = {};
_.each(results, function (result) {
totals.push([
result.timestamp.valueOf(),
result.data.total
]);
_.each(result.data.accounts, function (account, accountId) {
if (!_.has(accounts, accountId)) {
accounts[accountId] = {
label: account.account.name,
data: []
};
}
accounts[accountId].data.push([
result.timestamp.valueOf(),
account.total
]);
});
_.each(_.omit(accounts, _.keys(result.data.accounts)), function (account) {
accounts[accountId].push([result.timestamp.valueOf(), 0]);
});
});
var data = [{
data: totals,
label: label
}];
_.each(accounts, function (account) {
data.push(account);
});
$(this > ".graph").plot(data, {
grid: {
hoverable: true
},
tooltip: {
show: true
},
xaxis: {
mode: "time"
},
yaxis: {
axisLabel: label
},
legend: {
container: $(this > ".legend")
},
selection: {
mode: "x"
}
});
});
// Reload function within pollData function
document.getElementById(apiRoot + "-reload").addEventListener("click", function() {
pollData(apiRoot,increment,startDate,label);
dateNow(".status","M/D/YYYY H:mm Z");
}, false);
}
//Load Reports
pollData('total-installs', 'day', oneMonth, "Total Installs");
pollData('installs', 'hour', twoWeeks, "Installs (by hour)");
pollData('sessions', 'day', twoWeeks, "Sessions");
pollData('sessions-length', 'day', twoWeeks, "Session Length");
// Selection Feature
// TODO: Add Zoom to selection
// TODO: Overview of Available Data when zoomed.
placeholder.bind("plotselected", function (event, ranges) {
var begin = ranges.xaxis.from.valueOf();
var end = ranges.xaxis.to.valueOf();
$(this).siblings(".selection").text(moment(begin).format("M/D/YYYY H:mm Z") + " to " + moment(end).format("M/D/YYYY H:mm Z"));
/*
var zoom = $("#zoom").prop("checked");
if (zoom) {
$.each(plot.getXAxes(), function(_, axis) {
var opts = axis.options;
opts.min = ranges.xaxis.from;
opts.max = ranges.xaxis.to;
});
plot.setupGrid();
plot.draw();
plot.clearSelection();
}
*/
});
// Update on Initial Poll
// TODO: Ask for location and provide local time.
dateNow(".status","M/D/YYYY H:mm Z");
});
You have no accountId parameter in your function
function (account) {
accounts[accountId].push([result.timestamp.valueOf(), 0]);
I'm trying to create a button class that extends an AbstractComponent class using $.extend() but the functions in AbstractComponent aren't available when I'm constructing the button.
The specific error I'm receiving is:
Uncaught TypeError: Object [object Object] has no method 'setOptions'
var Button = {};
var abstract = new AbstractComponent;
$.extend(Button,abstract);
//debugger;
//this.setOptions is available here
Button = function(options) {
'use strict';
var defaultOptions = {
templateName: '#button-tmpl',
title: "Label goes here",
type: "primary",
size: "medium",
disabled: null,
autosave: null,
href: null,
onclick: null
};
//debugger
//this.setOptions is not available here
this.setOptions(options, defaultOptions);
this.checkRequiredKeys('title');
return this;
};
Button.prototype.updateOptions = function() {
var options = this.options;
if (options.href === null) {
options.href = 'javascript:;';
}
if (options.disabled === null) {
options.disabled = 'disabled';
}
if (options.autosave === true) {
options.autosave = 'ping-autosave';
}
};
AbstractComponent.js
var AbstractComponent = function() {
console.log('this will be the constructor for elements extending this class');
};
AbstractComponent.prototype.show = function() {
this.render();
};
AbstractComponent.prototype.close = function() {
// stop listeners and remove this component
this.stopListening();
this.remove();
};
AbstractComponent.prototype.getTemplateName = function() {
return this.options.templateName;
};
AbstractComponent.prototype.checkRequiredKeys = function() {
var errors = new Array();
if (typeof this.getTemplateName() === "undefined") {
errors.push('templateName');
}
for (var i = 0; i < arguments.length; i++) {
if (!this.options.hasOwnProperty(arguments[i])) {
errors.push(arguments[i]);
}
}
if (errors.length > 0) {
throw new Exception("Required property(s) not found:" + errors.join(', ') + " in " + this.toString());
}
};
AbstractComponent.prototype.getElement = function() {
'use strict';
if(!this.options.updated) {
this.updateOptions();
}
return new AbstractView(this.options).render().$el;
};
AbstractComponent.prototype.updateOptions = function() {
this.options.updated = true;
return true;
};
AbstractComponent.prototype.getHtml = function() {
return this.getElement().html();
};
AbstractComponent.prototype.setOptions = function(options, defaultOptions) {
this.options = _.defaults(options, defaultOptions);
};
AbstractComponent.prototype.toString = function() {
return "Component" + this.getTemplateName() + "[id=" + this.options.id + "]";
};
jQuery extend is for moving properties from one (or more) object(s) to another object.
$.extend({}, {
foo: 10,
bar: 20
});
You should use prototypal inheritance isntead
function Button(options) {
'use strict';
var defaultOptions = {
templateName: '#button-tmpl',
title: "Label goes here",
type: "primary",
size: "medium",
disabled: null,
autosave: null,
href: null,
onclick: null
};
//debugger
//this.setOptions is not available here
this.setOptions(options, defaultOptions);
this.checkRequiredKeys('title');
return this;
};
Button.prototype = new AbstractComponent;