Vue order list view - list

I've been working with vue js 1.27 on a project and I need to be able to sort a list by numeric values, Alphabetic and reverse order.
I've been trying this all day with not really much progress.
I've left some of my previous code in my code sample to let you know what I've already attempted.
{% extends 'base.html.twig' %}
{% block body %}
<div id="wrap">
<h2>Select a category</h2>
<ul id="categorySelect">
<li v-for="cat in categories | orderBy reverse" #click="selectCategory(cat)" class="${cat.selectedCategory == category ? 'selected' : ''}">${cat.title}</li>
</ul>
</div>
{% endblock %}
{% block javascripts %}
<script type="text/javascript">
Vue.config.delimiters = ['${', '}'];
new Vue({
el: '#wrap',
data: {
//reverse: -1,
wasClicked: true,
selectedCategory: null,
categories: [{
title: 'ALL',
category: null
},
{
title: 'CATE',
category: 'sport'
},
{
title: 'DOG',
category: 'sport'
},
{
title: 'SPEED',
category: 'sport'
},
{
title: 'CAT',
category: 'sport'
},
{
title: 'SPORT',
category: 'sport'
},
{
title: 'ART',
category: 'sport'
},
{
title: 'PEOPLE',
category: 'people'
},
{
title: 'CAR',
category: 'car'
}]
},
filters: {
categoryFilter: function (infoBlocs) {
return this.wasClicked ? this.categories : {};
},
caseFilter: function () {
if (this.wasClicked) {
return this.reverseArray();
}
return this.alphaSortByKey(this.categories, 'category');
},
reverse: function(value) {
// slice to make a copy of array, then reverse the copy
return value.slice().reverse();
}
},
methods: {
selectCategory: function(category) {
//this.wasClicked =! this.wasClicked;
//this.categories = this.alphaSortByKey(this.categories, 'category');
// if (this.reverse) {
// this.categories = this.alphaSortByKey(this.categories, 'category');
// }
// else {
// this.categories = this.reverseArray();
// }
if (this.reverse) {
this.categories = this.alphaSortByKey(this.categories, 'category');
this.reverse = false;
}
else {
this.categories = this.reverseArray();
//this.reverse = true;
}
},
alphaSortByKey: function (arr, key) {
arr.sort(function (a, b) {
if (a[key] < b[key])
return -1;
if (a[key] > b[key])
return 1;
return 0;
});
return arr;
},
reverseArray: function () {
return this.categories.reverse();
},
changeOrder: function (event) {
var self = this;
self.reverse = self.reverse * -1
var newItems = self.categories.slice().sort(function (a, b) {
var result;
if (a.name < b.name) {
result = 1
}
else if (a.name > b.name) {
result = -1
}
else {
result = 0
}
return result * self.reverse
})
newItems.forEach(function (item, index) {
item.position = index;
});
this.categories = newItems;
}
}
});
</script>
{% endblock %}

Here is a fiddle with working functionality to sort and reverse the order of your array. For reverse I just used the built in reverse() Javascript function. For the alphanumeric sort I borrowed the solution from this answer: https://stackoverflow.com/a/4340339/6913895
https://jsfiddle.net/n1tbmgo9/
Html:
<div id="wrap">
<h2>Select a category</h2>
<button #click="sort">
Sort alphanumeric
</button>
<button #click="reverse">
Reverse list
</button>
<ul id="categorySelect">
<li v-for="cat in categories">${cat.title}</li>
</ul>
</div>
Javascript:
Vue.config.delimiters = ['${', '}'];
new Vue({
el: '#wrap',
data: {
selectedCategory: null,
categories: [{
title: 'ALL',
category: null
},
{
title: 'CATE',
category: 'sport'
},
{
title: 'DOG',
category: 'sport'
},
{
title: 'SPEED',
category: 'sport'
},
{
title: 'CAT',
category: 'sport'
},
{
title: 'SPORT',
category: 'sport'
},
{
title: 'ART',
category: 'sport'
},
{
title: 'PEOPLE',
category: 'people'
},
{
title: 'CAR',
category: 'car'
}]
},
methods: {
sort: function () {
this.categories.sort(this.sortAlphaNum);
},
reverse: function () {
this.categories.reverse();
},
sortAlphaNum: function (a,b) {
var reA = /[^a-zA-Z]/g;
var reN = /[^0-9]/g;
var aA = a.title.replace(reA, "");
var bA = b.title.replace(reA, "");
if(aA === bA) {
var aN = parseInt(a.title.replace(reN, ""), 10);
var bN = parseInt(b.title.replace(reN, ""), 10);
return aN === bN ? 0 : aN > bN ? 1 : -1;
} else {
return aA > bA ? 1 : -1;
}
}
}
});
The built in reverse() function is straightforward so I will elaborate on the sortAlphaNum() sort function. That function is passed into the sort() function and must return either 1, 0, or -1 to indicate if the objects passed in should be moved in a particular direction in the array.
The variables reA and reN are regexes to identify alphabetic and numeric characters, respectively.
First the function removes all alphabet characters from the titles of the two objects passed in and compares them for equality.
var aA = a.title.replace(reA, ""); and
var bA = b.title.replace(reA, "");
If they are not equal then it means we have alphabet characters (as opposed to just numeric input) and we can sort them accordingly.
return aA > bA ? 1 : -1;
If the titles with alphabet characters stripped are equal (if(aA === bA)) then we remove numeric digits from the object titles (leaving non-numeric characters).
var aN = parseInt(a.title.replace(reN, ""), 10);
var bN = parseInt(b.title.replace(reN, ""), 10);
Then we compare the resulting variables and return the appropriate sorting value (1, 0, -1).
return aN === bN ? 0 : aN > bN ? 1 : -1;

create a computed property where you manually sort it and then loop for that instead the data prop

There is my List Component implementation with client-side order implementation:
<template>
<div>
<table class="table table-bordered" v-if="data.length">
<thead>
<tr>
<th v-for="(colValue, colKey) in cols" :key="colKey">
<a #click="sort(colKey)" href="javascript:void(0)">
{{colValue}}
<icon :name="(sortColumn === colKey) ? (sortAsc ? 'sort-down' : 'sort-up') : 'sort'"></icon>
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id">
<td v-for="(colValue, colKey) in cols" :key="row.id + colKey">{{row[colKey]}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import _ from 'lodash';
import apiServer from '#/utils/apiServer'; //
export default {
name: 'List',
data() {
return {
data: [],
sortColumn: '',
sortAsc: true
};
},
props: {
cols: {
type: Object,
required: true
},
apiEndpoint: {
type: String,
required: true
}
}
created() {
this.fetchData();
},
watch: {
'$route': 'fetchData'
},
methods: {
async fetchData() {
const response = await apiServer.get(this.apiEndpoint);
this.data = response.data;
},
sort(colKey) {
this.data = _.sortBy(this.data, [colKey]);
if (this.sortColumn === colKey) {
if (!this.sortAsc) {
this.data = _.reverse(this.data);
}
this.sortAsc = !this.sortAsc;
} else {
this.sortAsc = false;
}
this.sortColumn = colKey;
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>
Usage example
<template>
<div>
<h1>Orders</h1>
<List :cols="cols" api-endpoint="/orders" title="Orders" />
</div>
</template>
<script>
import List from '#/components/List.vue';
export default {
name: 'OrderList',
components: { List },
data() {
return {
cols: {
id: 'Id',
title: 'Title',
created_at: 'Created at'
}
};
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>

Related

Extend query function to support multiple search criteria in CouchDB/PouchDB

I'm trying to get a count of docs having level: 2, completed: true. However, I'm having trouble wrapping my head around introducing another criteria in the query function. Currently, as evident from the code; it is just printing out docs having completed: true. How can I extend this query to support another query parameter like level too?
[{
_id: 1,
name: 'Test_01',
level: 1,
completed: false
},
{
_id: 2,
name: 'Test_02',
level: 2,
completed: true
},
{
_id: 3,
name: 'Test_01',
level: 3,
completed: false
}]
const myMapReduceFun = {
map: (doc) => {
emit(doc.completed);
}.toString(),
reduce: '_count'
};
db.query(myMapReduceFun, {
key: true, reduce: true
})
.then((result) => {
console.log(result)
})
This is easily done with map/reduce. One strategy is to use complex keys, the other using clever demarcations in a string.
I prefer complex keys as it does not require having to assemble the key or other string based monkey business.
Consider the design document in the demo:
{
_id: "_design/my_index",
views: {
completed_str: {
map: `function (doc) {
emit(doc.completed + '/' + doc.level + '/')
}`,
},
completed_complex: {
map: `function (doc) {
emit([doc.completed,doc.level])
}`,
},
},
}
completed_str uses concatenation and a '/' to create two fields for completed and level
completed_complex uses an array to create a complex key
In the snippet below I've included an example of both approaches. The key (no pun intended) is to emit the 'completed' field first, then the 'level' field.
When toying with the queries, do note the difference in the value Key field returned by the view.
const gel = id => document.getElementById(id);
const g_view_result = 'view_result';
function getQuery() {
let view = gel('view').value;
let completed = gel('completed').value === 'true';
let level = parseInt(gel('level').value, 10);
if (view === 'complex') {
// use complex key view
return {
view: "my_index/completed_complex",
params: {
reduce: false,
include_docs: false,
start_key: [completed, level],
end_key: [completed, level],
}
}
}
// use simple string view
return {
view: "my_index/completed_str",
params: {
reduce: false,
include_docs: false,
start_key: [completed, level, ''].join('/'),
end_key: [completed, level, ''].join('/'),
}
}
}
async function query() {
try {
let html = [];
const view_result = gel(g_view_result);
view_result.innerText = '';
let query = getQuery();
let docs = await db.query(query.view, query.params);
html.push(['ID', 'Key'].join('\t'));
html.push(['----', '--------'].join('\t'));
docs.rows.forEach(row => {
html.push([row.id, row.key].join('\t'));
})
view_result.innerText = html.join('\n');
} catch (e) {
console.log('err: ' + e);
}
}
// canned test documents
function getDocsToInstall() {
return [{
_id: "1",
name: 'Test_01',
level: 1,
completed: false
},
{
_id: "2",
name: 'Test_02',
level: 2,
completed: true
},
{
_id: "3",
name: 'Test_01',
level: 3,
completed: false
},
{
_id: "4",
name: 'Test_4',
level: 3,
completed: true
},
{
_id: "5",
name: 'Test_05',
level: 2,
completed: true
},
{
"_id": "_design/my_index",
"views": {
"completed_str": {
"map": `function (doc) {
emit(doc.completed + '/' + doc.level + '/')
}`
},
"completed_complex": {
"map": `function (doc) {
emit([doc.completed,doc.level])
}`
}
}
}
]
}
let db;
async function initDb() {
db = new PouchDB('test', {
adapter: 'memory'
});
return db.bulkDocs(getDocsToInstall());
}
(async() => {
try {
await initDb();
} catch (e) {
console.log(e);
}
})();
<script src="https://cdn.jsdelivr.net/npm/pouchdb#7.1.1/dist/pouchdb.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<label for="completed">Completed:</label>
<select name="completed" id="completed">
<option value="true">True</option>
<option value="false">False</option>
</select>
<label for="level">Level:</label>
<select name="level" id="level">
<option value="0">0</option>
<option value="1">1</option>
<option value="2" selected>2</option>
<option value="3">3</option>
</select>
<label for="view">View:</label>
<select name="view" id="view">
<option value="complex">Complex Key</option>
<option value="simple">Simple String Key</option>
</select>
<button id="query" onclick="query()">Query</button>
<div style='margin-top:2em'></div>
<pre id='view_result'>
</pre>

You modified *** twice in a single render

After upgrading to 1.13 I get this exception and I can't figure out what's the issue. I also couldn't find any helpful resource which tackles my issue.
It happens for properties I set in another computed property. But this property is definitely called only once.
I have created a jsbin example: http://emberjs.jsbin.com/roderameya/edit?html,js,console,output
UPDATE
As requested I post some code which is more close to my real implementation.
Ember.Controller.extend({
filter: '',
resultCount: {
total: 0,
matches: 0,
mismatches: 0
},
results: function() {
var items = this.get('model'),
matches = [],
resultCount = {};
// Apply search filter
matches = items.filter(function(item){
// Just a dummy filter function
return true;
});
// We need the total number matched by the filter string
resultCount.total = matches.length;
// The already matched items must be narrowed further down
matches = matches.filter(function(item) {
// Another filter function
return true;
});
resultCount.matches = matches.length;
resultCount.mismatches = resultCount.total - matches.length;
this.set('resultCount', resultCount);
return matches;
}.property('model', 'filter'),
});
Try to have your properties not set other properties, but rather depend on each other:
App.IndexController = Ember.Controller.extend({
count: function() {
return this.get("data.length") || 0;
}.property('data.length'),
data: [1,2,3]
});
Updated jsbin for you.
UPDATE
Basically, your resultCount is a temporary variable that we can get rid of, and the rest are just chaining computed properties together:
updated jsbin for advanced example
code:
// Index controller
App.IndexController = Ember.Controller.extend({
count: Ember.computed('filteredItems.length', function(){
return this.get('filteredItems.length');
}),
data: [
Ember.Object.create({ name: "jim", age: 15 }),
Ember.Object.create({ name: "jeff", age: 42 }),
Ember.Object.create({ name: "eric", age: 7 })
],
filter: RegExp(".*"),
ageFilter: -1,
mismatchCount: Ember.computed('filteredItems.length', 'secondPassFilteredItems.length', function() {
return this.get('filteredItems.length') - this.get('secondPassFilteredItems.length');
}),
filteredItems: Ember.computed('data', 'filter', function() {
var controller = this;
return this.get('data').filter(function(item) {
return item.get('name').match(controller.get("filter"));
});
}),
secondPassFilteredItems: Ember.computed('filteredItems', 'ageFilter', function() {
var controller = this;
var ageFilter = controller.get("ageFilter");
if (Ember.isEqual(ageFilter, -1)) {
return this.get('filteredItems');
} else {
return this.get('filteredItems').filter(function(item) {
// more filtering
return item.get("age") <= ageFilter;
});
}
}),
results: Ember.computed.alias('secondPassFilteredItems'),
actions: {
filterByJ: function() {
this.set('filter', new RegExp("j"));
},
filterByEric: function() {
this.set('filter', new RegExp("eric"));
},
filterAllNames: function() {
this.set('filter', new RegExp(".*"));
},
filterYoungins: function() {
this.set('ageFilter', 15);
},
filterAllAges: function() {
this.set('ageFilter', -1);
}
}
});
Template usage:
<script type="text/x-handlebars" data-template-name="index">
<p>Results found: {{count}}</p>
<p>Diff between first and second filter: {{mismatchCount}}</p>
<p>First Filter:
<button {{action 'filterAllNames'}}>all people</button>
<button {{action 'filterByJ'}}>People with J in name</button>
<button {{action 'filterByEric'}}>People named 'eric'</button>
</p>
<p> Second Filter:
<button {{action 'filterAllAges'}}>all ages</button>
<button {{action 'filterYoungins'}}>15 years old or younger</button>
</p>
<ul>
{{#each results as |item|}}
<li>{{item.name}} is {{item.age}} years old</li>
{{/each}}
</ul>
</script>

The changed value is not reflecting on the input field in ReactJS, TestUtils

I am using ReactJs and Mocha and trying few unit tests. I have two mandatory form fields First Name and Last Name. I am trying to test the validation where if only one field is entered, the other field displays the missing value validation error.
Below code simulates the changes to the value and simulates the form submit.
TestUtils.Simulate.change(firstNameElement , {target: {value: 'Joe'}});
TestUtils.Simulate.submit(formElement)
The changed value is reflected in the event handlers on First Name. But, not in the test. So, both the fields display missing value validation failing my test.
What I could be doing wrong here?
Below are code:
//NameForm.jsx
'use strict';
var React=require('react')
var forms = require('newforms')
var NameForm = forms.Form.extend({
firstName: forms.CharField({maxLength: 100, label: "First name(s)"}),
lastName: forms.CharField({maxLength: 100, label: "Last name"}),
cleanFirstName(callback) {
callback(null)
},
render() {
return this.boundFields().map(bf => {
return <div className={'form-group ' + bf.status()}>
<label className="form-label" htmlFor={bf.name}>
<span className="form-label-bold">{bf.label}</span>
</label>
{bf.errors().messages().map(message => <span className="error-message">{message}</span>)}
<input className="form-control" id={bf.name} type="text" name={bf.name} onChange = {this.onChangeHandler}/>
</div>
})
}
, onChangeHandler: function(e){
console.log("onchnage on input is called ----- >> " + e.target.value)
}
})
module.exports = {
NameForm
}
Here is NamePage.jsx:
'use strict';
var React = require('react')
var {ErrorObject} = require('newforms')
var superagent = require('superagent-ls')
var {API_URL} = require('../../constants')
var {NameForm} = require('./NameForm')
var NamePage = React.createClass({
contextTypes: {
router: React.PropTypes.func.isRequired
},
propTypes: {
data: React.PropTypes.object,
errors: React.PropTypes.object
},
statics: {
title: 'Name',
willTransitionTo(transition, params, query, cb, req) {
if (req.method != 'POST') { return cb() }
superagent.post(`${API_URL}/form/NameForm`).send(req.body).withCredentials().end((err, res) => {
if (err || res.serverError) {
return cb(err || new Error(`Server error: ${res.body}`))
}
if (res.clientError) {
transition.redirect('name', {}, {}, {
data: req.body,
errors: res.body
})
}
else {
transition.redirect('summary')
}
cb()
})
}
},
getInitialState() {
return {
client: false,
form: new NameForm({
onChange: this.forceUpdate.bind(this),
data: this.props.data,
errors: this._getErrorObject()
})
}
},
componentDidMount() {
this.setState({client: true})
},
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
var errorObject = this._getErrorObject(nextProps.errors)
this.refs.nameForm.getForm().setErrors(errorObject)
}
},
_getErrorObject(errors) {
if (!errors) { errors = this.props.errors }
return errors ? ErrorObject.fromJSON(errors) : null
},
_onSubmit(e) {
e.preventDefault()
var form = this.state.form
form.validate(this.refs.form, (err, isValid) => {
if (isValid) {
this.context.router.transitionTo('name', {}, {}, {
method: 'POST',
body: form.data
})
}
})
},
render() {
return <div>
<h1 className="heading-large">Your name</h1>
<form action='name' method="POST" onSubmit={this._onSubmit} ref="form" autoComplete="off" noValidate={this.state.client}>
{this.state.form.render()}
<button type="submit" className="button">Next</button>
</form>
</div>
},
})
module.exports = NamePage
Here is NameTest.js :
//NameTest.js
var React = require('react')
var ReactAddons = require('react/addons')
var TestUtils = React.addons.TestUtils
var InputFieldItem = require('../../src/page/name/NamePage')
describe('Name page component', function(){
var renderedComponent;
before('render element', function() {
console.log("*** in before")
renderedComponent = TestUtils.renderIntoDocument(
<InputFieldItem />
);
});
it('Only First Name entered should display one error message', function() {
renderedComponent = TestUtils.renderIntoDocument(
<InputFieldItem />
);
var formElement = TestUtils.findRenderedDOMComponentWithTag(renderedComponent, 'form').getDOMNode()
var firstNameElement = TestUtils.scryRenderedDOMComponentsWithTag(renderedComponent, 'input')[0].getDOMNode()
var lastNameElement = TestUtils.scryRenderedDOMComponentsWithTag(renderedComponent, 'input')[1].getDOMNode()
var buttonElement = TestUtils.findRenderedDOMComponentWithTag(renderedComponent, 'button').getDOMNode()
TestUtils.Simulate.change(firstNameElement , {target: {value: 'Joe'}});
TestUtils.Simulate.submit(formElement)
var errorSpans = TestUtils.scryRenderedDOMComponentsWithClass(renderedComponent, 'error-message')
console.log("First name value is :|"+ firstNameElement.value + "|")
expect (errorSpans.length).to.equal(1)
})
});

How to use dom-repeat with objects instead of arrays in Polymer 1.0?

Iterating over an array myarray=[1, 2, 3] works like this:
<template is="dom-repeat" items="[[myarray]]">
<span>[[item]]</span>
</template>
How can I iterate over an object myobject = {a:1, b:2, c:3}?
Here is a complete implementation:
<test-element obj='{"a": 1, "b": 2, "c": 3}'></test-element>
<dom-module id="test-element">
<template>
<template is="dom-repeat" items="{{_toArray(obj)}}">
name: <span>{{item.name}}</span>
<br> value: <span>{{item.value}}</span>
<br>
<hr>
</template>
</template>
<script>
Polymer({
properties: {
obj: Object
},
_toArray: function(obj) {
return Object.keys(obj).map(function(key) {
return {
name: key,
value: obj[key]
};
});
}
});
</script>
</dom-module>
I faced the same problem, but my use-case is a bit more demanding: I need two-way deep binding through the repeat. Plus I cannot afford rewriting the whole tree on each change.
Since I did not find a solution and the polymer team seems to take it slowly on this issue, I made something for the time being. It's written in ES2015, but translating that to vanilla ES5 should be straightforward. Runs in Chrome anyway as is. Or throw it at bable. This page details how. The gist for the purpose of this posting:
vulcanize element.html --inline-script --inline-css | \
crisper -h element.v.html -j element.js;
babel element.js -o element.js
So here we go:
<link rel="import" href="../../bower_components/polymer/polymer.html">
<dom-module id="my-objarray">
<script>
(function() {
'use strict';
class Objarray {
beforeRegister() {
this.is = 'my-objarray';
this.properties = {
array:{
notify:true,
type:Array,
value:function() {return new Array();}
},
object:{
notify:true,
type:Object
}
};
this.observers = ['_onArray(array.*)', '_onObject(object.*)'];
}
_onObject(change) {
if(this._setting) return;
if(change.path == "object") this._rewriteArray();
else this._writeElement(change);
}
_rewriteArray() {
this.splice("array", 0, this.array.length);
for(let i in this.object) {
this.push("array", {key:i, value:this.object[i]});
}
}
_writeElement(change) {
const path = change.path.match(/^object\.([^\.]+)(.*)$/);
const key = path[1];
const objectPath = "object." + key + (path[2] || "");
const id = this._getId(key);
const arrayPath = "array." + id + ".value" + (path[2] || "");
this.set(arrayPath, this.get(objectPath));
}
_getId(key) {
const collection = Polymer.Collection.get(this.array);
for(const element of this.array) {
if((element && element.key) === key) {
return collection.getKey(element);
}
}
}
_onArray(change) {
let path = change.path.match(/^array\.(#\d+)\.([^\.]+)(\.|$)/);
if(!path) return;
let id = path[1], field = path[2];
if(field == "key") throw new Error("my-objarray: Must not change key!");
if(field != "value") throw new Error("my-objarray: Only change inside value!");
this._setting = true;
this.set(this._getPath(change, id), change.value);
delete this._setting;
}
_getPath(change, id) {
let collection = Polymer.Collection.get(change.base);
let index = change.base.indexOf(collection.getItem(id));
let key = change.base[index].key;
return change.path.replace("array." + id + ".value", "object." + key);
}
}
Polymer(Objarray);
})();
</script>
</dom-module>
Usage:
<dom-module id="my-objarray-test">
<template strip-whitespace>
<my-objarray object="{{items}}" array="{{array}}"></my-objarray>
<template is="dom-repeat" items="{{array}}">
<div>
<label>{{item.key}}:</label>
<input type="number" value="{{item.value.data::input}}">
</div>
</template>
</template>
<script>
(function() {
'use strict';
class ObjarrayTest {
beforeRegister() {
this.is = 'my-repeat-test';
this.properties = {
items:{
notify:true,
type:Object,
value:function() {return new Object();}
}
};
this.observers = ['_onItems(items.*)'];
}
ready() {
console.log("my-repeat-test.ready");
this.items = {a:{data:1}, b:{data:2}};
}
_onItems(change) {console.log("test._onItems", change.path);}
}
Polymer(ObjarrayTest);
})();
</script>
</dom-module>
Hope that helps somebody. Presumable polymer now gets the feature like tomorrow :-)
I've been using Object.keys(obj).map(function(prop){return {id:prop, val:obj[prop]}})
Revisiting this to account for issues others have mentioned. This is compatible with all browsers and uses hasOwnProperty.
<template is="dom-repeat" items="[[_toArray(obj)]]">
key: [[item.key]] val: [[item.val]]
</template>
...
_toArray: function(obj, deep) {
var array = [];
for (var key in obj) {
if (deep || obj.hasOwnProperty(key)) {
array.push({
key: key,
val: obj[key]
});
}
}
return array;
}
You need to turn this object into a meaningful array to be able to iterate over it with the dom-repeat.
I have created a myObj property with the initial value. I have then created a property called myObjAsArray which is an empty array. In the ready callback function which is called when the local dom is ready, I am iterating over all of the properties of myObj and adding them to myObjAsArray (see here for how to iterate through an objects properties). You can then iterate over this array with dom-repeat.
<link rel="import" href="bower_components/polymer/polymer.html">
<dom-module id="test-element">
<style>
</style>
<template>
<template is="dom-repeat" items="{{myObjAsArray}}">
name: <span>{{item.name}}</span>
value: <span>{{item.value}}</span>
</template>
</template>
</dom-module>
<script>
Polymer({
is: "test-element",
properties: {
myObj: {
type: Object,
value: function () {
return {
a: 1,
b: 2,
c: 3
};
}
},
myObjAsArray: {
type: Array,
value: function () {
return [];
}
}
},
attached: function () {
var propArray = [];
for (var prop in this.myObj) {
if (this.myObj.hasOwnProperty(prop)) {
propArray.push({name: prop, value: this.myObj[prop]});
}
}
this.myObjAsArray = propArray;
}
});
</script>
Object.keys() doesn't seem to work in IE. So modified the implementation to use _.map instead.
<test-element obj='{"a": 1, "b": 2, "c": 3}'></test-element>
<dom-module id="test-element">
<template>
<template is="dom-repeat" items="{{getKeyValue(obj)}}">
key: <span>{{item.key}}</span>
<br> value: <span>{{item.value}}</span>
<br>
<hr>
</template>
</template>
<script>
Polymer({
properties: {
obj: Object
},
getKeyValue: function(obj) {
return _.map(obj, function(value, key) {
return {
key: key,
value: value
};
});
}
});
</script>
</dom-module>
https://jsfiddle.net/avidlearner/36jnb16d/

Custom Autocomplete Component Ember Cli not working

I not sure why the return is not being passed to the component template. It's listening and the value is found but its not working.
return result.resources; is not returning the data when result.resoures has an array of objects.
here is the autocomplete template auto-complete.hbs
<ul>
<li class='row input-group-lg'>
{{input type="text" value=searchText placeholder="Enter Street Address" class='col-md-6 col-md-offset-3 col-xs-12 street-address'}}
</li>
</ul>
<ul>
{{#each searchResults}}
<li>{{this.name}}</li>
{{/each}}
</ul>
here is auto-complete.js
import Ember from 'ember';
export default Ember.Component.extend({
searchText: null,
searchResults: function() {
var searchText = this.get('searchText');
if(!searchText) {
return;
}
Ember.$.ajax({
url: "http://dev.virtualearth.net/REST/v1/Locations",
dataType: "jsonp",
data: {
key: "",
q: searchText
},
jsonp: "jsonp"
}).then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
return result.resources;
}
}
});
}.property('searchText')
});
The code block
.then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
return result.resources;
}
}
will return from the promise and will not return the value for the computed property, which obviously means that you are not returning anything for the CP.
A possible work around can be
searchResults: function() {
var searchText = this.get('searchText');
var searchResults = Ember.ArrayProxy.create();
if(!searchText) {
return;
}
Ember.$.ajax({
url: "http://dev.virtualearth.net/REST/v1/Locations",
dataType: "jsonp",
data: {
key: "",
q: searchText
},
jsonp: "jsonp"
}).then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
searchResults.set('content',result.resources);
}
}
});
return searchResults;
}.property('searchText')
You can create an arrayproxy and return the arrayproxy for the CP. Upon completion of the promise, set the result as content to the arrayproxy, which will update the template.