I learned that instead of using model.findAll and write code in call back function of findAll we can achieve same by using new model.List({}).
E.g., jsfiddle --> http://jsfiddle.net/CRZXH/48/ .. in this jsfiddle example List implementation works but findOne fails.
var people = new Person.List({});
return can.Component({
tag: 'people',
template: initView,
scope: {
people: people
}
})
Above example works fine, initially people is assigned with empty object but after ajax call complete people variable is updated with list and view updates on its own.
How to achieve the same in case of findOne?
var person = PersonModel.findOne({});
can.Component({
tag: 'person',
template: initView,
scope: person
})
This fails....
I did work around as below :
var person;
PersonModel.findOne({},function(data){
person = data
});
can.Component({
tag: 'person',
template: initView,
scope: person
})
This works only if I add asyn=false in findeOne ajax call.
I got solution for this problem from http://webchat.freenode.net/ #daffl
Solution : http://jsfiddle.net/CRZXH/49/
can.Model.extend('Person', {
findOne: 'GET api/metadata',
getMetadata: function() {
var result = new Person();
Person.findOne().then(function(data) {
result.attr(data.attr(), true);
});
return result;
}
}, {});
// Person component which uses findOne
can.Component({
tag: 'person',
scope: function() {
return {
person: Person.getMetadata()
}
}
})
1- the ID for findOne is mandatory
findOne({id: modelId})
2- You can put the person model in a viewmodel (AKA component scope) and not passe the value use can.stache plugin and can.map.define plugin for this
can.fixture({
"GET api/people":function(){
return [
{id: 1, name: "Person 1"},
{id: 2, name: "Person 2"}
];
},"GET api/people/{id}":function(request,response){
return {id: request.data.id, name: "Person "+request.data.id}
}
});
can.Model.extend('Person',{
findAll: 'GET api/people',
findOne: 'GET api/people/{id}',
},{});
can.Component.extend({
tag:'x-person',
scope:{
define:{
person:{
get:function(currentPerson,setFn){
Person.findOne({id: 2}, setFn);
}
}
}
}
});
var frag=can.view('personTmpl',{});
$('#main').html(frag);
Here is the fiddle http://jsfiddle.net/cherif_b/egq85zva/
Related
I try to use hightchart in spring boot to plot a line graph. But I can't get line chart but only show blank result in my view.
This is my view side
$.ajax({
url:'${pageContext.request.contextPath}/hawker/linechartdata',
dataType: 'json'
success: function(result){
var month = JSON.parse(result).month;
var report = JSON.parse(result).report;
drawLineChart(month, report);
}
})
function drawLineChart(month,report){
document.addEventListener('DOMContentLoaded', function () {
var chart = Highcharts.chart('container', {
chart: {
type: 'line'
},
title: {
text: 'Fruit Consumption'
},
xAxis: {
categories: month
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: [{
name: 'Jane',
data: report
}]
});
});
}
<!--Here to show the graph -->
<div id="container"></div>
My controller that use Json
#RequestMapping("/linechartdata")
#ResponseBody
public String getDataFromDb(#AuthenticationPrincipal UserDetails user) {
User currentuser = userRepository.findByUsername(user.getUsername());
Optional<Hawker> hawker = hawkerService.getHawkerByUserId(currentuser.getId());
//Get the report list
List<Report> reportList = reportService.findByHawId(hawker.get().getHaw_id());
JsonArray jsonMonth = new JsonArray();
JsonArray jsonReport = new JsonArray();
JsonObject json = new JsonObject();
reportList.forEach(report->{
jsonMonth.add(report.getMonth()+1);
jsonReport.add(report.getTotal_sales());
});
json.add("month", jsonMonth);
json.add("report", jsonReport);
return json.toString();
When I remove the ajax and add in the manual data, it do show the graph. Please help me, Thank you.
You don't need to manually create json in your controller code, spring boot will handle it for you. You should create a dto class in a form which is expected by your javascript.
Which is in your case:
public class LineChartDto {
private List<Integer> month; // you better call this "months" or "monthList"
private List<BigDeciamal> report; // why do you call this "report" while this is actually "sales"
// all args constructor, getters
}
And your controller method would be:
#RequestMapping("/linechartdata")
#ResponseBody // or use #RestController
public LineChartDto getSalesLineChartData(..) {
List<Report> reportList = ..
List<Integer> months = reportList.stream()
.map(report -> report.getMonth()+1)
.collect(Collectors.toList());
List<BigDecimal> sales = reportList.stream()
.map(Report::getTotal_sales) // better name this getTotalSales
.collect(Collectors.toList());
return new LineChartDto(months, sales);
}
Response will result in json object:
{
"month": [1, 2, ... ],
"report": [100, 200, ... ]
}
As for why you ajax doesn't work - the question is too broad. Start with Chrome Dev Tools Network to check what network communication is happening, check browser console for errors, add some console.log() or better debug your js.
This line document.addEventListener('DOMContentLoaded', function () looks suspicious to me. I don't think you need to addEventListener each time you call your chart.
Probably you should do this:
function drawLineChart(month,report){
Highcharts.chart('container', {
...
I am trying to understand how a collection passed in to a view is being referenced and used. There does not seem to be any reference to the collection, but it's models are being used. Also, I'm unable to get the collection items to be bound/displayed when I use my collection that is bound to my api, but it works when I use the hard coded collection. Is it because I need to fetch my collection at some point? My collection is fine and the path is fine. I use it throughout my app without any problems.
Below is the code:
module.exports = Backbone.Collection.extend({
model: Employee,
url:"api/employees"
});
MainView
module.exports = base.extend({
el: '#content',
template:template,
initialize: function () {
// var items = new EmployeeCollection([
// {id: 1, firstName: "Test1 fName", lastName: "Test1 lName"},
// {id: 2, firstName: "Test2 fName", lastName: "Test2 lName"},
// {id: 3, firstName: "Test3 fName", lastName: "Test3 lName"}
// ]);
EmployeeListCollection = new EmployeeCollection();
//this.itemView = new EmployeeListView({collection: items}); //this syntax works if I uncomment the code above to create my item list
this.itemView = new EmployeeListView({collection: EmployeeListCollection}); //do i need to fetch the collection at some point?
this.render();
},
render: function(){
this.el.innerHTML = Mustache.to_html(this.template);
this.itemView.render();
$("#empList").html(this.itemView.el);
}
});
ItemListView - where does the passed in collection get referenced? I see a model reference, but I passed in a collection.
module.exports = base.extend({
//tagName:'ul',
tagName:'select',
initialize: function(){
_.bindAll(this, "renderItem");
},
renderItem: function(model){
console.log('employeelistloop render');
this.itemView = new EmployeeListItemView({model: model});
this.itemView.render();
$(this.el).append(this.itemView.el);
},
render: function(){
this.collection.each(this.renderItem);
},
});
Actually, I think I figured what the problem was. I did indeed need to fetch the collection in my ItemListView. And I also realize now that render is being called before renderitem and that each model in the collection is being passed in to the renderitem function. I was able to get my collection to work by calling fetch in my render:
var self = this;
this.collection.fetch().done(function(){
self.collection.each(self.renderItem);
});
So it all makes sense now. But I still don't fully understand why my code runs twice. When I do a console.log in the initialize and render of MainView, I get two calls every time the first time I run it.
I have asked a variant of this question here. But basically I need to create a computed property that operated on a hasMany association. I need to do sorting similar to the javascript sort function; where I can do something like
files = ["File 5", "File 1", "File 3", "File 2"];
files.sort(function(a,b){
return parseInt(b.split(' ').pop()) - parseInt(a.split(' ').pop())
});
result:
["File 5", "File 3", "File 2", "File 1"]
Here is my jsbin:
http://emberjs.jsbin.com/simayexose/edit?html,js,output
Any help would be greatly appreciated.
Note:
My jsbin presently is not working correctly (for reasons other then this question). I have posted a question about that here. I just did not want to hold up an answer to this question.
Update 1
Thanks #engma. I implemented the instructions. As a matter of fact, I copied and pasted what was posted. This is the new jsbin.
http://emberjs.jsbin.com/roqixemuyi/1/edit?html,js,output
I still do not get anything sorted, though. And even if it did, it still would not have sorted the way I would like it.
I need something like the following: (below are errors that I get when I try to implement this in my code, not from jsbin, since I can not get jsbin to work)
sortedFiles: function(){
return this.get('files').sort(function(a,b){
return parseInt(b.split(' ').pop()) - parseInt(a.split(' ').pop());
});
}.property('files.#each.name')
When I do this I get the following error:
Uncaught TypeError: this.get(...).sort is not a function
So since this.get('files') returns a promise, I figured I would try this;
sortedFiles: function(){
return this.get('files').then(function(files){
return files.sort(function(a,b){
return parseInt(b.split(' ').pop()) - parseInt(a.split(' ').pop());
});
});
}.property('files.#each.name')
But then I get the following error:
Uncaught Error: Assertion Failed: The value that #each loops over must be an Array. You passed {_id: 243, _label: undefined, _state: undefined, _result: undefined, _subscribers: }
BTW, I am using emberjs v1.11.0
And, the sortBy I am using is ember-cli/node_modules/bower-config/node_modules/mout/array/sortBy.js
Here is the code for it
var sort = require('./sort');
var makeIterator = require('../function/makeIterator_');
/*
* Sort array by the result of the callback
*/
function sortBy(arr, callback, context){
callback = makeIterator(callback, context);
return sort(arr, function(a, b) {
a = callback(a);
b = callback(b);
return (a < b) ? -1 : ((a > b) ? 1 : 0);
});
}
module.exports = sortBy;
Update 2
So to answer the question how to do an Emberjs advanced sort hasMany association as a computed property; I had to change
this.get('files').sort(function(a,b){
...
});
return this.get('files').toArray().sort(function(a,b){
...
});
This allowed me to use the javascript sort and return the desired sorted objects.
Ok first of all your JSBin had many issues so lets go throw them one by one
1- you did not include any Ember-Data build, so I included 1, this is needed for the fixtures and the models
<script src="http://builds.emberjs.com/tags/v1.0.0-beta.15/ember-data.js"></script>
2- Your Scripts
var App = window.App = Ember.Application.create({
});
//First this is how to register the adapter
App.ApplicationAdapter = DS.FixtureAdapter.extend({});
App.IndexRoute = Ember.Route.extend({
model: function() {
//Second with find you pass in the ID so I am using 1
//if you want to get all folders use findAll()
return this.store.find('folder',1);
}
});
App.IndexController = Ember.Controller.extend({
});
App.Router.map(function() {
});
App.Folder = DS.Model.extend({
name: DS.attr('string'),
files: DS.hasMany('file',{async:true}),
sortedFiles: function(){
//Sorty By has no second parameter, if you need more sorting power, do it your self
return this.get('files').sortBy('name');
}.property('files.#each.name')
});
App.File = DS.Model.extend({
name: DS.attr('string'),
folder: DS.belongsTo('folder',{async:true})
});
App.File.FIXTURES = [
{
id: 1,
name: 'File 5',
folder:1
},
{
id: 2,
name: 'File 1',
folder:1
},
{
id: 3,
name: 'File 3',
folder:1
},
{
id: 4,
name: 'File 2',
folder:2
},
{
id: 5,
name: 'File 6',
folder:2
},
{
id: 6,
name: 'File 4',
folder:2
}
];
App.Folder.FIXTURES = [
{
id: 1,
name: 'Folder 1',
files:[1,2,3]
},
{
id: 2,
name: 'Folder 2',
files:[4,5,6]
}
];
Your Template:
<div>
Folders: <br>
<ul>
<li>
Name: {{model.name}} <br>
Files:
{{!-- here we access the sorted files property in the model--}}
{{#each file in model.sortedFiles}}
{{file.name}} <br/>
{{/each}}
</li>
</ul>
</div>
I am working on new version of an app using legacy API (I have no controll of what the API returns etc.. ).
On the app init I request & store some site-wide info the factory which I have called stateFactory. Inside the stateFactory there is categories property (array of objects) which is storing the id -> category name relation.
Inside my app template I am using a filter to extract the name of the category by id {{ cat_id | categoryNameByIdFilter }} which is doing a lookup in the stateFactory.categories and returns the category name.
How do I write a unit test for such functionality (jasmine, mocha, chai, anything)?
// represantion of what the stateFactory looks like with some data in it
app.factory('stateFactory', ['', function(){
return {
categories = [
{ cat_id: 1, cat_name: "some category name" },
{ cat_id: 2, cat_name: "another category name" }
];
};
}])
// categoryNameByIdFilter
app.factory('categoryNameByIdFilter', ['stateFactory', function(stateFactiry){
return function(cat_id){
if ( !cat_id ) return null;
var cat_obj = _.findWhere(stateFactiry.categories, {
id: cat_id
});
if ( !cat_obj ) return null;
return cat_obj.cat_name;
};
}]);
I suggest using Jasmine and angular's mock module. You can create a mock of the stateFactory so that it does not hit a web service while unit testing. I have used Sinon to create my mocks and spies. You can, then, have angular inject your mock instead of the real service. This way, the only system under test is the categoryNameByIdFilter and not your web service.
// representation of what the stateFactory looks like with some data in it
app.factory('stateFactory', ['', function ()
{
return function ()
{
//This is the real stateFactory, which we are going to mock out.
};
}]);
// categoryNameByIdFilter - The system under test in this example
app.factory('categoryNameByIdFilter', ['stateFactory', '_', function (stateFactiry, _)
{
return function (cat_id)
{
if (!cat_id) return null;
var cat_obj = _.findWhere(stateFactiry.categories, {
id: cat_id
});
if (!cat_obj) return null;
return cat_obj.cat_name;
};
}]);
Given the code above, we can test categoryNameByIdFilter by doing this...
describe("categoryNameByIdFilter", function ()
{
beforeEach(module('YOUR_APP_MODULE'));
beforeEach(function ()
{
//The following line creates a mock of what we expect the state factory to return.
//We're mocking this because it is no the system under test, the filter is.
//A sinon 'stub' is a spy
mockStateFactory = sinon.stub({
categories: [
{ id: 1, cat_name: "some category name" },
{ id: 2, cat_name: "another category name" }
]
});
module(function ($provide)
{
//When Angular asks for a stateFactory, give them this mock instead
$provide.value('stateFactory', mockStateFactory);
});
});
//You can inject a filter using the "inject" method below
it("should filter by id", inject(function (categoryNameByIdFilter)
{
//Wrap categoryNameByIdFilter in a spy so that we can make assertions off of it.
var spy = sinon.spy(categoryNameByIdFilter);
var result = spy(1);
expect(result).toEqual("some category name");
expect(spy.calledBefore(mockStateFactory)).toBeTruthy();
expect(spy.returned("some category name")).toBeTruthy();
sinon.assert.calledOnce(spy);
spy(2);//Returns something besides "some category name"
expect(spy.alwaysReturned("some category name")).not.toBeTruthy();
sinon.assert.calledTwice(spy);
}));
});
Have json-data like this:
{ tournaments: [ {
tournament_id: "..."
tournament_name: "..."
events: [ {
event_id: ...
event_name: ....
param : [ {
param_a :
param_b : ..
subparan : [ {
sub_1: 1
sub_2 : 2...
So. I don't understand - how to it implement into BackBone Collection/Model style?
How to handle change sub_1? - Made Collection of Collection of Collection?
Simpliest way described in backbone tutorial:
var Events = Backbone.Collection.extend({
initialize: function(){
this.params = new Params()
}
})
var Tournaments = Backbone.Model.extend({
initialize: function(){
this.events = new Events()
}
})
var tournaments = new Tournaments()
You can continue nesting by you needs. When I was working on similar task I wrap each collection in model representing collection state and change itself in answer of collection events. This allows not to asking nested collections about its state having actual state in model.
var CollModel = Backbone.Model.extend({
defaults: {
state = ''//or list or dict or whatever
},
initialize: function(){
this.items = new Backbone.Collection();
this.items.on('all', this.setState, this)
},
setState: function(){
this.set(
'state',
this.items.reduce(function(state, item){
/*calculate state*/
}, '')
)
},
info: function(){
return this.get('state')
}
})
So you can nest collection-models with similar technic and read their state directly through instance.info() depends on how you calculate it. Your top model state will be updated from cascade updates of underneath models-collections.