The objective is to show a filtered list of items for each possible state. I'm trying a template because such a list may need to display in many places. The template calls filterItems to get its particular list of items.
The filterItems code below uses a field in the viewModel (current_filter), so all the lists have the same items (3 and 8 which are in a ready state) :( That's not the idea. Each template instance has $data which contains the correct filter value. Is there a way to make this filter value available to the filterItems function?
<script>
var viewModel = {
items: ko.observableArray(
[
{ "iid": 1, "state": "entered" },
{ "iid": 3, "state": "ready" },
{ "iid": 4, "state": "delivered" },
{ "iid": 8, "state": "ready" },
{ "iid": 13, "state": "entered" }
]),
states: ko.observableArray(
[
{ "sid": 1, "filter": "entered", "color": "yellow" },
{ "sid": 2, "filter": "ready", "color": "red" },
{ "sid": 3, "filter": "delivered", "color": "blue" }
]),
current_filter: 'ready'
}
viewModel.filterItems = ko.computed(function () {
var filtered_items = [];
for (var i = 0; i < viewModel.items().length; ++i)
if(viewModel.items()[i].state == viewModel.current_filter)
filtered_items.push(viewModel.items()[i]);
return filtered_items;
}, viewModel);
</script>
<div data-bind="foreach: states">
<div data-bind="template: {name: 'state', data: $data}"></div>
<script type="text/html" id="state">
<h2 data-bind="text: filter"></h2>
<ul data-bind="foreach:viewModel.filterItems()">
<li>
<div data-bind="text: iid"></div>
</li>
</ul>
</script>
</div>
<script>ko.applyBindings(viewModel);</script>
jsFiddle at https://jsfiddle.net/mthrock/kxpfdfva/8/#&togetherjs=JKJOos6VJc
how about a component instead of a template?
run snippet below
ko.components.register('mycomponent', {
viewModel: function(params) {
var self = this;
this.items = ko.observableArray(
[{
"iid": 1,
"state": "entered"
}, {
"iid": 3,
"state": "ready"
}, {
"iid": 4,
"state": "delivered"
}, {
"iid": 8,
"state": "ready"
}, {
"iid": 13,
"state": "entered"
}]);
this.filter = params.filter;
this.filterItems = ko.computed(function() {
var filtered_items = [];
for (var i = 0; i < this.items().length; ++i)
if (this.items()[i].state == this.filter)
filtered_items.push(this.items()[i]);
return filtered_items;
}, this);
},
template: ' <h2 data-bind="text: filter"></h2>\
<ul data-bind="foreach:filterItems()">\
<li>\
<div data-bind="text: iid"></div>\
</li>\
</ul>'
});
function model() {
var self = this;
this.states = ko.observableArray(
[{
"sid": 1,
"filter": "entered",
"color": "yellow"
}, {
"sid": 2,
"filter": "ready",
"color": "red"
}, {
"sid": 3,
"filter": "delivered",
"color": "blue"
}]);
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div data-bind="foreach: states">
<mycomponent params="filter: filter"></mycomponent>
</div>
Related
I have a simple scatterplot with two datasets: active and passive:
const data = {
"datasets": [{
"label": "Active",
"sentences": [
"A1",
"A2",
"A3"
],
"data": [
[
"0.4340433805869016",
"0.12813240157479788"
],
[
"-0.39983629799199083",
"0.12125799115087213"
],
[
"-0.04289228113339527",
"0.10106119377169194"
]
],
"borderColor": "#43a047",
"backgroundColor": "#7cb342"
},
{
"label": "Passive",
"sentences": [
"P1",
"P2",
"P3"
],
"data": [
[
"0.4295487808020268",
"0.19271652809947026"
],
[
"-0.4438451670978469",
"-0.08848766134414247"
],
[
"-0.10789534989054622",
"0.08013654263956245"
]
],
"borderColor": "#1e88e5",
"backgroundColor": "#039be5"
}
],
"labels": []
};
new Chart(document.getElementById("sentences"), {
type: "scatter",
data: data,
options: {
responsive: true,
plugins: {
legend: {
position: "top",
},
tooltip: {
callbacks: {
label: ctx => ctx.dataset.sentences[ctx.dataIndex]
}
}
}
}
});
(https://jsfiddle.net/br5dhpwx/)
Currently this renders fine as is:
However, I want to draw a line between the corresponding data points on mouseover. I.e. A1-P1, A2-P2, A3-P3, etc.
It should look something like this:
I tried to use the setHoverStyle event, but, so far, wasn't successful.
You can use a custom plugin for this:
const EXPANDO_KEY = 'customLinePlugin';
const data = {
"datasets": [{
"label": "Active",
"sentences": [
"A1",
"A2",
"A3"
],
"data": [
[
"0.4340433805869016",
"0.12813240157479788"
],
[
"-0.39983629799199083",
"0.12125799115087213"
],
[
"-0.04289228113339527",
"0.10106119377169194"
]
],
"borderColor": "#43a047",
"backgroundColor": "#7cb342"
},
{
"label": "Passive",
"sentences": [
"P1",
"P2",
"P3"
],
"data": [
[
"0.4295487808020268",
"0.19271652809947026"
],
[
"-0.4438451670978469",
"-0.08848766134414247"
],
[
"-0.10789534989054622",
"0.08013654263956245"
]
],
"borderColor": "#1e88e5",
"backgroundColor": "#039be5"
}
],
"labels": []
};
const plugin = {
id: "customLine",
afterInit: (chart) => {
chart[EXPANDO_KEY] = {
index: null
}
},
afterEvent: (chart, evt) => {
const activeEls = chart.getElementsAtEventForMode(evt.event, 'nearest', {
intersect: true
}, true)
if (activeEls.length === 0) {
chart[EXPANDO_KEY].index = null
return;
}
chart[EXPANDO_KEY].index = activeEls[0].index;
},
beforeDatasetsDraw: (chart, _, opts) => {
const {
ctx
} = chart;
const {
index
} = chart[EXPANDO_KEY];
if (index === null) {
return;
}
const dp0 = chart.getDatasetMeta(0).data[index]
const dp1 = chart.getDatasetMeta(1).data[index]
ctx.lineWidth = opts.width || 0;
ctx.setLineDash(opts.dash || []);
ctx.strokeStyle = opts.color || 'black'
ctx.save();
ctx.beginPath();
ctx.moveTo(dp0.x, dp0.y);
ctx.lineTo(dp1.x, dp1.y);
ctx.stroke();
ctx.restore();
}
}
new Chart(document.getElementById("sentences"), {
type: "scatter",
data: data,
options: {
responsive: true,
plugins: {
customLine: {
dash: [2, 2],
color: 'red',
width: 2
},
legend: {
position: "top",
},
tooltip: {
callbacks: {
label: ctx => ctx.dataset.sentences[ctx.dataIndex]
}
}
}
},
plugins: [plugin]
});
<body>
<canvas id="sentences"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.2/chart.js"></script>
</body>
EDIT:
For some reason stack snippet in creation works but doesnt like so itself, so here is a fiddle link: https://jsfiddle.net/Leelenaleee/btux41dz/
My server is returning a json data and I have no problem loading the models (page.js, event.js, choice.js) with Ember Data. But when the form is submitted, the JSON data submitted to the server doesn't contain the related models (event.js, choice.js).
Below are my files and the json data.
Json data returned by backend api:
{
"data": {
"type": "pages",
"id": "12345",
"attributes": {
"guest_id": null,
"name": null,
"email": null,
"address": null
},
"relationships": {"events": {"data": [
{
"type": "events",
"id": "67891"
},
{
"type": "events",
"id": "90908"
}
]}}
},
"included": [
{
"type": "events",
"id": "67891",
"attributes": {
"event_id": "67891",
"name": "Event 1"
},
"relationships": {"choices": {"data": [
{
"type": "choices",
"id": "67891-11111"
},
{
"type": "choices",
"id": "67891-22222"
}
]}}
},
{
"type": "events",
"id": "90908",
"attributes": {
"event_id": "90908",
"name": "Event 2"
},
"relationships": {"choices": {"data": [
{
"type": "choices",
"id": "90908-11111"
},
{
"type": "choices",
"id": "90908-22222"
}
]}}
},
{
"type": "choices",
"id": "67891-11111",
"attributes": {
"choice_id": "67891-11111",
"name": "Diet choice",
"value": "0"
},
"relationships": null
},
{
"type": "choices",
"id": "",
"attributes": {
"choice_id": "67891-22222",
"name": "No. of adult guest",
"value": "0"
},
"relationships": null
}
{
"type": "choices",
"id": "90908-11111",
"attributes": {
"choice_id": "90908-11111",
"name": "Diet choice",
"value": "0"
},
"relationships": null
},
{
"type": "choices",
"id": "90908-22222",
"attributes": {
"choice_id": "90908-22222",
"name": "No. of adult guest",
"value": "0"
},
"relationships": null
}
]
}
JSON data submitted to the server
{
"data": {
"id":"e47e8358-0f18-4607-b958-2877155bf5be",
"attributes":{
"guest_id":null,
"name":"my name",
"email":"myemail#gmail.com",
"address":"myaddress"
},
"relationships":{
"events":{
"data":[
{
"type":"events",
"id":"67891"
},
{
"type":"events",
"id":"90908"
}
]
}
},
"type":"pages"
}
}
/pages/show.hbs
<p>
<label>Name: </label>
{{input type="text" value=model.name id="name"}}
</p>
{{#each model.events as |event|}}
<h3>
{{event.name}}
<!-- Rounded switch -->
<label class="switch">
<input type="checkbox" class="switch_input" id="{{event.id}}">
<span class="slider round"></span>
</label>
</h3>
{{#each event.choices as |choice|}}
{{#if (is-equal choice.name "Diet choice")}}
<p>
<label for="diet_choice">{{choice.name}}:</label>
<select id="diet_choice" value=choice.value>
<option value="anything">Anything and Everything</option>
<option value="vegetarian">Vegetarian</option>
<option value="hala">Hala</option>
</select>
</p>
{{/if}}
{{#if (is-equal choice.name "No. of adult guest")}}
<p>
Adult guest
<div>
<button type="button" name="btnMinusGuest" {{action "minusCounter" choice 0 "Minimum 0 guest"}}>-</button>
{{input type="text" value=choice.value}}
<button type="button" name="btnPlusGuest" {{action "addCounter" choice 1 "Maximum 1 guest"}}>+</button>
</div>
</p>
{{/if}}
{{/each}}
{{/each}}
<p>
<label for="email">Email:</label>
{{input type="text" value=model.email}}
</p>
<p>
<label for="address">Address:</label>
{{input type="text" value=model.address}}
</p>
<p>
<input type="submit" name="btnSubmit" value="Submit" {{action "submit"}} />
<input type="submit" name="btnCancel" value="Cancel" {{action "cancel"}} />
</p>
{{outlet}}
/routes/pages/show.js
import Route from '#ember/routing/route';
export default Route.extend({
queryParams: {
event: ''
},
model(params) {
return this.get('store').findRecord('page', params.page_id, { adapterOptions: {query: {'event': params.event}}});
},
actions: {
submit() {
// Create rec
page.save().then(function() {
console.log('submitted');
}).catch(function(reason) {
console.log(reason);
});
},
cancel() {
alert("Are you sure?");
},
addCounter(item, max_val, msg) {
let current_val = parseInt(item.get('value'));
if (current_val >= max_val) {
alert(msg)
} else {
item.set('value', current_val + 1);
}
},
minusCounter(item, min_val, msg) {
let current_val = parseInt(item.get('value'));
if (current_val <= min_val) {
alert(msg);
} else {
item.set('value', current_val - 1)
}
},
}
});
/models/page.js
import DS from 'ember-data';
export default DS.Model.extend({
guest_id: DS.attr(),
name: DS.attr(),
email: DS.attr(),
address: DS.attr(),
is_e_invite: DS.attr(),
data_time_submitted: DS.attr(),
events: DS.hasMany('event')
});
/models/event.js
import DS from 'ember-data';
export default DS.Model.extend({
event_id: DS.attr(),
name: DS.attr(),
choices: DS.hasMany('choice')
});
/models/choice.js
import DS from 'ember-data';
export default DS.Model.extend({
choice_id: DS.attr(),
name: DS.attr(),
value: DS.attr()
});
One way to solve it, is to save each model independently, as #Lux has said in a comment, but you can also write a custom serializer which will semi-manually prepare a data to be pushed after saving a parent model.
Assuming you're using ember-data, it should probably look something like this (see https://guides.emberjs.com/v3.0.0/models/customizing-serializers/):
Create a file called serializers/page.js and put there:
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
events: {embedded: 'save'}
},
serializeIntoHash: function (data, type, record, options) {
const object = this.serialize(record, options);
for (let key in object) {
data[key] = object[key];
}
},
serializeHasMany: function (record, json, relationship) {
const key = relationship.key;
const hasManyRecords = record.hasMany(key);
if (!hasManyRecords || !this.attrs[key] || this.attrs[key].embedded !== 'save') {
return this._super(record, json, relationship);
}
json[key] = [];
hasManyRecords.forEach(item => {
let recordData = item.serialize({includeId: true});
if (relationship.options.deepEmbedded) {
relationship.options.deepEmbedded.forEach(deepKey => {
if (!item.hasMany(deepKey)) {
return true;
}
const deepRecords = item.hasMany(deepKey);
recordData[deepKey] = [];
deepRecords.forEach(deepRecord => {
recordData[deepKey].push(deepRecord.serialize({includeId: true}));
});
});
}
json[key].push(recordData);
});
}
});
and in your page model change events: DS.hasMany('event') to events: DS.hasMany('event', {deepEmbedded: ['choices']}).
Above serializer tells ember-data to put each event of a page as a serialized map into output map of a page. In addition, for each event, it also walks through each deepEmbedded model of a relationship - choices in this case - and also serializes them.
Note: the deepEmbedded option is not part of ember-data and I created it for my specific needs, so it may not work perfectly in every case.
I try to display a line chart with two yAxis in Chart.js.
My Code:
window.onload = function() {
var ctx = document.getElementById("chart").getContext("2d");
var myChart = new Chart(ctx, {
type: "line",
data: {
"labels": [
"01.12.2015",
"02.12.2015",
"03.12.2015",
"04.12.2015",
"30.12.2015"
],
"datasets": [{
"label": "DEA Burrweiler Druck Abgabe",
"fill": "false",
yAxisID: "y-axis-0",
"data": [
8.7913,
8.6985,
8.7914,
8.7948,
8.7882
]
}, {
"label": "DEA Burrweiler Druck Zulauf",
"fill": "false",
yAxisID: "y-axis-0",
"data": [
4.5997,
4.5526,
4.5915,
4.5937,
4.5795
]
}, {
"label": "DMS Flemlingen Durchfluss",
"fill": "false",
yAxisID: "y-axis-1",
"data": [
1.9588,
2.4226,
2.1392,
2.223,
1.9048
]
}]
},
options: {
yAxes: [{
position: "left",
"id": "y-axis-0"
}, {
position: "right",
"id": "y-axis-1"
}]
}
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.0/Chart.min.js"></script>
<canvas id="chart" width="400" height="400"></canvas>
The error says: "TypeError: yScale is undefined"
When I remove the yAxisID options in the datasets, the Chart gets displayed but only with one yAxis.
window.onload = function() {
var ctx = document.getElementById("chart").getContext("2d");
var myChart = new Chart(ctx, {
type: "line",
data: {
"labels": [
"01.12.2015",
"02.12.2015",
"03.12.2015",
"04.12.2015",
"30.12.2015"
],
"datasets": [{
"label": "DEA Burrweiler Druck Abgabe",
"fill": "false",
"data": [
8.7913,
8.6985,
8.7914,
8.7948,
8.7882
]
}, {
"label": "DEA Burrweiler Druck Zulauf",
"fill": "false",
"data": [
4.5997,
4.5526,
4.5915,
4.5937,
4.5795
]
}, {
"label": "DMS Flemlingen Durchfluss",
"fill": "false",
"data": [
1.9588,
2.4226,
2.1392,
2.223,
1.9048
]
}]
},
options: {
yAxes: [{
position: "left",
"id": "y-axis-0"
}, {
position: "right",
"id": "y-axis-1"
}]
}
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.0/Chart.min.js"></script>
<canvas id="chart" width="400" height="400"></canvas>
I have the yAxis defined in the chart options. So whats the Problem, what am I missing?
The yAxes should be under a property scales under options, like so
...
options: {
scales: {
yAxes: [{
..
Fiddle - http://jsfiddle.net/dahd27d7/
In my ember app I have a models:
App.Schedule = DS.Model.extend({
manager:DS.belongsTo('App.Manager', { embedded: true }),
entries:DS.hasMany('App.Reservation', { embedded: true })
});
App.Reservation = DS.Model.extend({
name:DS.attr('string')
});
and a handelbars view:
{{#each schedule in controller}}
<td>
{{#each reservation in schedule.entries)}}
<div>{{reservation.name}}</div>
{{/each}}
</td>
{{/each}}
But with this view a've got exception
Expecting 'ID', got 'undefined'
This workaround works, but I know this is wrong way.
{{#each reservation in schedule._data.hasMany.entries}}
Any ideas?
EDIT.
After Mike Aski answer.
My JSON, returning from backend.
{
"schedules": [
{
"id": "476a3881-4fe8-42f5-8bdb-650d38f911e8",
"entries": [
{
"name": "test1",
"begin": "2012-11-22T10:00:00+06:00",
"end": "2012-11-22T11:00:00+06:00",
"id": "71c6da83-8ae2-4210-90f8-e65b06f819d7"
},
{
"name": "test2",
"begin": "2012-11-22T12:00:00+06:00",
"end": "2012-11-22T14:00:00+06:00",
"id": "d234c8c0-66f5-4e98-b921-4472b39a98f7"
}
]
},
{
"id": "8d9b1539-8a1f-4a5d-acfc-5918e61e3990",
"entries": []
},
{
"id": "a279d9a5-ea88-4012-8094-8a30125fd32b",
"entries": []
}
]
}
Did you ensure you have an id property in the reservations JSON objects?
You should have them, even if relations are embedded.
I have an array object in View with binding from Controller. When I change the array in controller, the array in view change properly. But my template for view that seems to be broken: http://jsfiddle.net/secretlm/2C4c4/84/
When I try to use rerender method in view. My template isn't broken. This fiddle works well: http://jsfiddle.net/secretlm/2C4c4/85/
HTML:
<script type="text/x-handlebars" data-template-name="test">
<button {{action "changeContent" target="App.latController"}}> Change</button>
{{#each element in view.content}}
<!-- if openTag is true, <ol> tag is created -->
{{#if element.openTag}}
<ol>
{{/if}}
<li>{{element.teo.item.Name}}</li>
<!-- if closeTag is true, </ol> tag is created -->
{{#if element.closeTag}}
</ol>
{{/if}}
{{/each}}
</script>
Javascript:
App = Ember.Application.create({});
App.LatView = Ember.View.extend({
templateName: "test",
contentBinding: "App.latController.contentWithIndices",
onRerender: function() {
this.rerender();
console.log(this.get('content'));
}.observes('content')
});
App.latController = Ember.Object.create({
changeContent: function() {
this.set('model', []);
var model = this.get('model');
var obj1 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test1",
"Name": "The name1"
}
};
var obj2 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test2",
"Name": "The name2"
}
}
var obj3 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test3",
"Name": "The name3"
}
}
var obj4 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test4",
"Name": "The name4"
}
}
var obj5 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test5",
"Name": "The name5"
}
}
model.pushObject(obj1);
model.pushObject(obj2);
model.pushObject(obj3);
model.pushObject(obj4);
model.pushObject(obj5);
},
model: [
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test1",
"Name": "The name1"
}
}
,
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test2",
"Name": "The name2"
}
}
,
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test3",
"Name": "The name3"
}
},
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test4",
"Name": "The name4"
}
},
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test5",
"Name": "The name5"
}
} ],
contentWithIndices: function() {
var length = this.get('model').length;
return this.get('model').map(function(i, idx) {
return {
teo: i,
openTag: (idx % 4 == 0),
closeTag: ((idx % 4 == 3) || (idx == length - 1))
};
});
}.property('model.#each')
});
var view = App.LatView.create({});
view.append();
When should we use rerender method manually? Is there any way to do that without re-rendering my view? Any idea is welcomed, thanks in advance.
I have tried to rewrite your example by using another strategy http://jsfiddle.net/davidsevcik/FjB3E/
Instead of counting the indices I used the Array.slice function to create groups of four items. It also makes the code clearer.
<script type="text/x-handlebars" data-template-name="test">
<button {{action "changeContent" target="App.latController"}}> Change</button>
{{#each elementGroup in view.content}}
<ol>
{{#each element in elementGroup}}
<li>{{element.item.Name}}</li>
{{/each}}
</ol>
{{/each}}
</script>
Javascript:
App = Ember.Application.create({});
App.LatView = Ember.View.extend({
templateName: "test",
contentBinding: "App.latController.contentSlicedBy4"
});
App.latController = Ember.Object.create({
changeContent: function() {
this.set('model', []);
var model = this.get('model');
var obj1 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test1",
"Name": "The name1"
}
};
var obj2 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test2",
"Name": "The name2"
}
}
var obj3 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test3",
"Name": "The name3"
}
}
var obj4 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test4",
"Name": "The name4"
}
}
var obj5 = {
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test5",
"Name": "The name5"
}
}
model.pushObject(obj1);
model.pushObject(obj2);
model.pushObject(obj3);
model.pushObject(obj4);
model.pushObject(obj5);
},
model: [
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test1",
"Name": "The name1"
}}
,
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test2",
"Name": "The name2"
}}
,
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test3",
"Name": "The name3"
}},
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test4",
"Name": "The name4"
}},
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test5",
"Name": "The name5"
}},
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test6",
"Name": "The name6"
}},
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test7",
"Name": "The name7"
}},
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test8",
"Name": "The name8"
}},
{
item: {
"DisplayOrder": 1,
"Public": true,
"Description": "The test9",
"Name": "The name9"
}}],
contentSlicedBy4: function() {
var result = [],
begin = 0,
model = this.get('model'),
slice = model.slice(begin, begin + 4);
while (!Em.empty(slice)) {
result.push(slice);
begin += 4;
slice = model.slice(begin, begin + 4);
}
return result;
}.property('model.#each', 'model')
});
var view = App.LatView.create({});
view.append();