Emberjs advanced sort hasMany association as a computed property - sorting

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>

Related

Kendo UI grid column template with nested evaluations - how to?

Kendo UI v2015.2.805
I have a KendoGrid with a template column that does a conditional to determine if a set of buttons should be added, if so additional evaluations are needed, and I can't figure out how to nest them.
The below works but does not have the required additional evaluation:
{ field: "Served", title: "Served",
template: "<div>" +
"#= (Kind==0 || Kind==7) ? '" +
"<button type=\"button\" data-id=\"12345\">Yes</button>" +
"<button type=\"button\" data-id=\"54321\">No</button>" +
"' : " +
"'NO BUTTON HERE'" +
"#</div>"
I multi-lined it to try to get it to look good, which it does not. The idea is that if the Kind = 0 or 7 then show two buttons otherwise do not. Works great.
However I need to evaluate the data-id to #= ID #, so I try:
" <button type=\"button\" data-id=\"' #= ID # '\">Yes</button>"
I know I need to 'drop out' of the quoted string to get the evaluation to work and since I have used double quotes for the whole expression I am returning the button in the conditional as a single quoted string, and as such escaping the button attributes, but I can't get it to evaluate the #=.
I've tried so many different combinations I've lost track.
So what is the 'right-way' to do this?
A SOLUTION:
Accepting David's answer with a modification to use template evaluation in the function:
{ field: "Served", title: "Served",
template: function (data) {
switch (data.Kind) {
case 0:
case 7:
var template = kendo.template("<button type='button' data-id='#= ID #' >Yes</button><button type='button' data-id='#= ID #'>No</button>");
return template(data);
default:
return '';
}
}
Having the function perform the initial test removes one level and allows 'normal' evaluation to occur.
You can use a function instead I Beleive it will would make things so much easier for you.
your template can be "#= buildButtons(data) #"
function buildButtons(model) {
if (model.Kind == 0 || model.Kind == 7) {
return "hello world";
}
return "";
}
here is a code sample
https://dojo.telerik.com/UQuqAfuv
<div id="grid"></div>
<script>
var people = [
{ id: 1, firstName: 'David', lastName: 'Lebee' },
{ id: 2, firstName: 'John', lastName: 'Doe' }
];
$('#grid').kendoGrid({
dataSource: {
transport: {
read: function(options) {
options.success(people);
}
}
},
columns: [
{ field: 'firstName', title: 'First Name' },
{ field: 'lastName', title: 'Last Name' },
{ title: 'Actions', template: '#= buildActions(data) #'}
]
});
function buildActions(model) {
if (model.firstName == "David") {
return 'Hello David';
}
return '';
}
</script>

Knockout dropdown cascade option not update

I'm trying to populate default value for dependent dropdown using knockout.
When values are harcoded it works, but I need to get values from ajax request and then, the second dropdown option is not updated. The value self.selectedState is updated but I guess that as I haven't already the options populated, then value in select is not bind. This is my code so far:
function myViewModel(country, state) {
var self = this;
self.selectedCountry = ko.observable();
self.selectedState = ko.observable();
self.availableCountries = ko.observableArray([
{
id: 1, name: 'United States', states: [
{ id: 1, name: 'Alabama' },
{ id: 2, name: 'California' },
]
},
{
id: 2, name: 'Canada', states: [
{ id: 53, name: 'Alberta' },
]
}
]);
self.availableStates = ko.observableArray([]);
self.selectedCountry.subscribe(function() {
self.availableStates([]);
for (var i = 0; i < self.availableCountries().length; i++) {
if (self.availableCountries()[i].id == self.selectedCountry()) {
self.availableStates(self.availableCountries()[i].states);
break;
}
}
});
self.selectedCountry(1).selectedState(2);
}
var viewModel = new myViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: availableCountries, optionsText: 'name', optionsValue: 'id', optionsCaption: 'Select a country...',
value: selectedCountry"></select>
<select data-bind="options: availableStates, optionsText: 'name',optionsValue: 'id', value: selectedState, visible: availableStates().length > 0" style="display:none"></select>
Is there something special that needs to be done when options are obtained from ajax?
jsfiddle
The issue isn't AJAX, specifically, but the fact that self.selectedState doesn't have a corresponding option for some time (while the options are being fetched).
From the docs:
Normally, when you use the value binding on a <select> element, it
means that you want the associated model value to describe which item
in the <select> is selected. But what happens if you set the model
value to something that has no corresponding entry in the list? The
default behavior is for Knockout to overwrite your model value to
reset it to whatever is already selected in the dropdown, thereby
preventing the model and UI from getting out of sync.
However, sometimes you might not want that behavior. If instead you
want Knockout to allow your model observable to take values that have
no corresponding entry in the <select>, then specify valueAllowUnset:
true. In this case, whenever your model value cannot be represented in
the <select>, then the <select> simply has no selected value at that
time, which is visually represented by it being blank.
This is a perfect opportunity to make use of a computed observable. A computed seems to a good fit for this job because it will save you from setting up your own manual subscriptions at track dependencies for you. Give it a try - I've changed very little of your code to accomplish this..
function myViewModel(country, state) {
var self = this;
self.selectedCountry = ko.observable();
self.selectedState = ko.observable();
self.availableCountries = ko.observableArray([{
id: 1,
name: 'United States',
states: [{ id: 1, name: 'Alabama' },
{ id: 2, name: 'California' }, ]
},
{ id: 2,
name: 'Canada',
states: [{ id: 53, name: 'Alberta' }, ]
}]);
self.availableStates = ko.computed(function() {
var states = [];
for (var i = 0; i < self.availableCountries().length; i++) {
if (self.availableCountries()[i].id == self.selectedCountry()) {
states = states.concat(self.availableCountries()[i].states);
break;
}
}
return states;
});
self.selectedCountry(1).selectedState(2);
}
var viewModel = new myViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: availableCountries, optionsText: 'name', optionsValue: 'id', optionsCaption: 'Select a country...',
value: selectedCountry"></select>
<select data-bind="options: availableStates, optionsText: 'name',optionsValue: 'id', value: selectedState, visible: availableStates().length > 0" style="display:none"></select>

Need more fields than 'className' '_objCount' and 'id' when making a find() query against Parse.com

When making a find() request against Parse.com, each of the results I get back only contain the following fields: 'className' '_objCount' and 'id'.
How to get all fields ? (I have more than 3 columns in the database browser)
How to get specific fields ?
UPDATE 1
1) Screenshot of the headers of my class, named "Code"
2) Query:
var Code = Parse.Object.extend('Code');
var query = new Parse.Query(Code);
query.equalTo('codeSet', codeSet);
query.find()
.then(function(codes) {
console.log(codes);
}
3) Results:
[ { className: 'Code', _objCount: 1, id: '6cMkEQxE6A' },
{ className: 'Code', _objCount: 2, id: 'uOU7osLvOo' },
{ className: 'Code', _objCount: 3, id: 'Vks4QEpBlh' },
{ className: 'Code', _objCount: 4, id: 'dWtukZhsHZ' } ]
UPDATE 2
The code is hosted in a heroku-hosted node.js instance
Use PFObject get method. like user.get('nickname');
https://parse.com/docs/js/api/classes/Parse.Object.html
Just incase anyone comes accross this problem, I'm going to post an answer as it took me several hours to find the solution, which is to use toJSON().
Example:
var Parse = require('parse/node');
var User = Parse.Object.extend("User");
const query = new Parse.Query(User);
await query.get("object-id-here")
.then((user) => {
// The object was retrieved successfully.
console.log("The User Object", user.toJSON())
}, (error) => {
// The object was not retrieved successfully.
console.log("Error:", error)
});

canjs findOne deferred

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/

Issues with knockout observable array validation

I have an issue with a validation rule that I put on observablearray elements. I'm using a custom message template to display the errors, the issue is that it doesn't get displayed on when the errors are there, however, I can see the '*' against the relevant field. Following is my model:
function ViewModel(item) {
var parse = JSON.parse(item.d);
var self = this;
this.ID = ko.observable(parse.ID).extend({ required: { params: true, message: "ID is required" }});
this.Name = ko.observable(parse.Name);
this.WeeklyData = ko.observableArray([]);
var records = $.map(parse.WeeklyData, function (data) { return new Data(data) });
this.WeeklyData(records);
}
var Data = function (data) {
this.Val = ko.observable(data).extend({
min: { params: 0, message: "Invalid Minimum Value" },
max: { params: 168, message: "Invalid Maximum Value" }
});
Here is the validation configuration I'm using:
// enable validation
ko.validation.configure({
registerExtenders: true,
messagesOnModified: false,
insertMessages: true,
parseInputAttributes: false,
messageTemplate: "customMessageTemplate",
grouping: { deep: true }
});
ko.validation.init();
Any my custom message template goes like this:
<script id="customMessageTemplate" type="text/html">
<em class="errorMsg" data-bind='visible: !field.isValid()'>*</em>
</script>
<ul data-bind="foreach: Errors">
<li class="errorMsg" data-bind="text: $data"></li>
</ul>
With this implementation, I don't see the validation messages in the custom template. But if I remove the configuration deep: true, it doesn't validate the observable array elements, but the other observable(ID) and then the message is displayed properly.
I'm very confused with this and a bit stuck, so appreciate if someone can help/
Thanks in advance.
What i understand from your question is that you are trying to validate your observableArray entries, so that if any entry in your WeeklyData observableArray fails the following condition:
arrayEntry % 15 === 0
you want to show error message. If this is the case, than the following custom validator can do the job for you :
var fminIncrements = function (valueArray) {
var check = true;
ko.utils.arrayFirst(valueArray, function(value){
if(parseInt(value, 10) % 15 !== 0)
{
check = false;
return true;
}
});
return check;
};
And you have to apply this validator on the observableArray (no need to set validation on individual entries of the array). This validator takes array as input and validate its each entry, if any one entry fails the validation it will retrun false and than you will see the error message.
Your viewmodel looks like :
function viewModel() {
var self = this;
self.WeeklyData = ko.observableArray([
15,
40
]).extend({
validation: {
validator: fminIncrements,
message: "use 15 min increments"
}
});
self.errors = ko.validation.group(self);
}
And here is the working fiddle.
the individual item is not showing the message - it's only in the summary - how do you specify the message on the individual textbox ?

Resources