My intention is to create a guided quiz but I would like to store things like names and choices completely locally. I followed the Polymer build an app in 30 minutes guide but they never cover how to persist data locally.
Any guidance or suggestions would be greatly appreciated.
I would suggest that you use json with an core-ajax element.
mydata.json
[{
"question":"A question here",
"answer":"The answer",
"other":"and so on.."
},
{
"question":"Another question here",
"answer":"The answer",
"other":"and so on.."
}]
index.html
<core-ajax id='ajax' url='mydata.json' on-response='{{response}}' handleAs='json'>
<template repeat='{{data in json}}'>
<p>{{data.question}}</p>
<p>{{data.answer}}</p>
</template>
<script>
Polymer({
json: null,
ready: function(){
this.$.ajax.go();
},
response: function(e){
this.json = e.detail.response;
}
});
</script>
Related
I have been fighting with ES6 trying to come up with, what should be, a pretty straightforward operation. I want to call JSON API data for Bitcoin from one of the three following websites:
https://cryptowat.ch
https://coinmarketcap.com/
https://www.cryptocompare.com/
All three sites API endpoints go straight to the price I want and I think this may be the problem. There is no array of data, just the specific price. In my example using #3 above, the only object is "USD". That being said, I think I'm overthinking the process as getting into APIs with much more data and arrays of data -- I have accomplished using ReactJS.
Trying to reach a single endpoint that shows up as the "State" in the React DOM Inspector as "USD" and is pulling in the correct price, I cannot get the price to render on the page even though ReactJS is seeing it and capturing it.
My code:
var BitcoinApp = React.createClass({
getInitialState: function() {
return {
"USD": []
}
},
componentDidMount: function() {
var th = this;
this.serverRequest =
axios.get(this.props.source)
.then(function(result) {
th.setState({
USD: result.data.USD
});
})
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<span>
{this.state.USD.map(function(Data) {
return (
<div key={Data.USD} className="testbtc">
<p>{Data.USD}</p>
</div>
);
})}
</span>
)
}
});
ReactDOM.render(<BitcoinApp source="https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD&e=Coinbase" />, document.querySelector("#btcPrice"));
I will mention that I have done a lot of research into this and have found a lot of answers -- all different! Everyone knows the ReactJS docs are severely outdated so finding the right path with ReactJS is difficult to say the least. Also, I'm using "axios" to "GET" the API data as I've read that "fetch" isn't globally supported yet? Is this still the case in 2017?
Using the above method, I can see this in the Inspector:
But when I go over to the "Console" portion of the inspector, I'm told that "this.state.USD.map is not a function".
I feel like I'm right on the cusp of solving this task, but I think I'm getting something wrong with the mapping of the promise.
the problem is that:
th.setState({
USD: result.data.USD
});
is seting not iterable object. I mean that this.state.USD.map is not a function means that USD is not an array (and you can see this in console).
Try this to see what happens:
th.setState({
USD: [result.data.USD]
});
However tho, you wrote:
There is no array of data, just the specific price.
then I think the best solution is to change just the render method and initial state:
render: function() {
return (
<span>
<div className="testbtc">
<p>{this.state.USD}</p>
</div>
</span>
)
}
getInitialState: function() {
return {
"USD": "",
}
},
<dom-module id="payment-list">
<template>
<template is="dom-repeat" items="{{clients}}">
<paper-item>
<span>{{item.Name}}</span>
|
<span>{{item.Amount}}</span>
</paper-item>
</template>
</template>
<script>
Polymer({
is: 'payment-list',
properties: {
clients: {
notify:true,
type: Array,
value: [{Name:'A', Amount:'100'},
{Name:'B', Amount:'200'}]
}
},
handleComplete: function(NewValues){
/***********/alert(NewValues);/***********/
},
ready: function(){
google.script.run.withSuccessHandler(this.handleComplete).GS_GetClients();
}
});
</script>
</dom-module>
I am using google.script.run to communicate with GAS function GS_GetClients(). GS_GetClients will be returning an object and I am trying to bind this new values to the property 'clients'.
When I do the alert I see that new values are passed to the handleComplete function from the server side GAS function. But I am not able assign the new values to the property 'clients'.
I cant set the values by using this.clients = NewValues. This is making the value to undefined.
The call to google.script.run is asynchronous.
The result seems to return, when the rendering of {{clients}} has already happend.
So in your success handler handleComplete(..) you somehow have to tell Polymer to redraw.
I do not know Polymer but from the docs Polymer data binding it seems as if you can do it like this :
Polymer({
...
setClients: function(clients) {
this.clients = clients;
// Notification required for binding to update!
this.notifyPath('clients', this.clients);
}
});
How to call custom methods in Polymer is explained here member-functions , sorry can not provide a more detailed answer regarding Polymer.
A flux architecture is trending in web applications and so is polymer elements.
Is there any example how to make a polymer application, which use flux architecture?
I've been thinking about using the Flux pattern with (Polymer) Web Components. Up to date I have come up with three possible solutions, all different from your way, so here they are:
DISCLAIMER I use Reflux library and not the Facebook's one.
Actions and Stores as elements
My first attempt was to make Flux pattern into elements so that any view, which need access to a store and invokes actions simply imports them.
<dom-module id="a-view">
<template>
<my-actions id="actions"></my-actions>
<my-store model="{{model}}"></my-store>
<span>{{model.property}}</span>
<button on-click="executeAction"></button>
</template>
</dom-module>
<script>
Polymer({
is: 'a-view',
executeAction: function() {
this.$.actions.doSomething({});
}
});
</script>
<my-actions> and <my-store> simply encapsulate actions and stores. There are some downsides to this method. First, potentially numerous non-visual elements are created, which can have detrimental effect on performance. Also creating those elements can be tricky if they should be Polymer elements, because they need static state. For a complete example see this repo
Flux without Flux
Recently I realized, again, what Web Components really are. With WC, your main API is the browser, namely elements, attributes and events. And Flux essentially is an event-driven data flow. So why not use Custom Events to communicate between custom elements? Here's an excerpt from my yesterday's plunk
<template is="dom-bind">
<a-view clicks="[[clicks]]" id="one"></a-view>
<a-view clicks="[[clicks]]" id="two"></a-view>
<a-view clicks="[[clicks]]" id="three"></a-view>
<a-store click-history="{{clicks}}"></a-store>
</template>
<script>
Polymer({
is: 'a-view',
properties: { clicks: Object },
fireClick: function() {
// invoking action is simply firing an event
this.fire('a-view-click', {});
}
});
Polymer({
is: 'a-store',
attached: function(){
document.addEventListener('a-view-click', function(ev) {
// do work and update store here
}.bind(this));
}
});
</script>
This is nice, because is not limited in any way to Polymer. Custom elements can be created with native API or other library and simply communicate with browser acting as your dispatcher. Of course this doesn't give you ways of synchronization out of the box, but is a simple and clean way without any clutter.
As you will see on Plunker, store updates by data-bindings. Another possibility is to fire off another event, though I'm not sure which would be better or when
Use Polymer's behaviors
Finally I've just had an idea, which improves upon the first, by replacing action/store custom elements by behaviors. There's no code yet, but here's a sketch:
var MyActionsBehaviour = PolymerFlux.createActions({ /*...*/ });
var MyStore = PolymerFlux.createStore({ /*...*/ });
Polymer({
is: 'a-view',
behaviours: [ MyActionsBehaviour, MyStore ],
onClick: function() {
this.behaviourAction.invoke({});
}
}});
Polymer({
is: 'a-store',
behaviours: [ MyActionsBehaviour, MyStore ],
attached: function() {
this.behaviourAction.listen(function() {
// 1. do work
// 2. update views
});
}
}});
I left the view updating part blank. It would likely take place by signalling an event but another possibility would be firing another action (Reflux has a nice concept of nested actions). Also I'm currently leaving the PolymerFlux.createActions and PolymerFlux.createStore for your imagination ;). The exact internals would ofc depend on the Flux implementation you choose.
I have made an attempt to use flux-type architecture in a polymer application.
Here is the main-app.html:
<link rel="import" href="./bower_components/polymer/polymer.html">
<link rel="import" href="store-cart.html">
<link rel="import" href="store-cart2.html">
<link rel="import" href="view-cart.html">
<link rel="import" href="view-additems.html">
<dom-module id="main-app">
<style>
</style>
<template>
<!-- Stores-->
<store-cart id="cart1" action=[[action]]></store-cart>
<store-cart2 id="cart2" action=[[action]]></store-cart2>
<!--Views and other stuff-->
<view-additems cart="cart1"></view-additems>
<view-additems cart="cart2" add="3"></view-additems>
<view-cart update="[[updateView]]"></view-cart>
</template>
</dom-module>
<script>
Polymer({
is: 'main-app',
properties: {
action: {
type: Object,
value: {}
},
updateView: {
value: ""
}
},
listeners: { //dispatcher event -> action
'viewAction': 'viewAction', // Action from view to be dispatched to the store/stores
'storeUpdated': 'storeUpdated' // storeUpdated-event from store to views
},
viewAction: function(e) {
action = e.detail;
switch (action.type) {
// "CombineCarts" is needed because both of the stores needs to be updated in order
case 'combineCarts':
this.$.cart1.addItems(this.$.cart2.nbItems);
this.$.cart1.updateViews();
this.$.cart2.emptyCart();
this.$.cart2.updateViews();
break;
// default action when store/stores can be updated independently
default:
this.action = action;
}
},
storeUpdated: function(e) {
this.updateView = e.detail;
}
});
</script>
The whole example: https://github.com/grohjy/polymer_flux_example
The main idea is that a "dispatcher" is located at the top most level of the polymer application and it's role is to redirect messages from stores to views and viceversa. Each store and view defines to which messages they reacts and how. At the dispatcher there is also an example how to update multiple stores in needed order.
The stores and some of the views are also located at the top most level of the application. A view can also have child views. A store shouldn't have any visual dom elements.
Please feel free to comment and share ideas.
I have an API that return HTML serialized in JSON accessed through a REST API. When I try to render the HTML from the API response, the HTML code is display as such:
<p>Hello this is a response!</p>, instead of as: Hello this is a response!
Is there any way to work around this?
Also, what are the potential security issues with doing this and actually render the HTML?
Best regards and help is much appreciated. :)
EDIT: Here are my models and my template. Sufficient to say, I'm new to Backbone.js, and this is mostly based on the Todos example.
Views:
app.DataView = Backbone.View.extend({
tagName: "li",
template: _.template($("#data-template").html()),
initialize: function() {
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
app.AppView = Backbone.View.extend({
el: "#htmldataapp",
initialize: function() {
app.datamodels.on('reset', this.addAll, this);
app.datamodels.fetch();
this.render();
},
addOne: function(datamodel) {
var view = new app.DataView({model: datamodel});
$('#data-list').append(view.render().el);
},
addAll: function() {
this.$('#data-list').html('');
app.datamodels.each(this.addOne, this);
}
});
Template:
<script type="text/template" id="data-template">
<%= data %>
</script>
You state that your api sends you HTML already?
So could it be that the data you get back is like <p>Hello this is a response!</p>, thus including the HTML tags?
If you would have underscore render this string in a template, it's no surprise you actually get the HTML tags to display.
Look at the generated source of the page, is the string Hello this is a response! wrapped in double <p> elements?
I would suggest changing your API in such a way that it returns data only (i.e. the string without the HTML), and have your HTML rendering be done by the underscore template engine.
Hope it helps!
EDIT:
I think you were using the underscore template function to often, causing the data to be rendered as a string.
Please see this fiddle for a sample setup how I think you should setup your app
Just getting started with Backbone and still making sense of the ins and outs.
I'm trying to simply display some JSON using Underscore and Backbone. I'm able to make it to work just using Underscore and $.getJSON, but when I try to wire it up with Backbone I get a variety of errors depending upon what I try.
I've also been able to get Backbone to work by hardcoding values in to the model, but I'm running in to a wall when I try to bring it all together. Any help is appreciated.
Here is my Underscore template:
<script type="text/html" id='trailTemplate'>
<% _.each(trails,function(trail){ %>
<%= trail.trailname %><br />
<% }); %>
</script>
And here is my Backbone code:
var Trail = Backbone.Model.extend({
urlRoot: "trails.json"
});
var trail = new Trail({});
var TrailView = Backbone.View.extend({
el: '.page',
template: _.template($("#trailTemplate").html(), {trails:trail.fetch()}),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var trailView = new TrailView({
model: trail
});
trailView.render();
And in case you need it, here is trails.json
[
{
"trailhead": "Bear Lake",
"trailname": "Bear Lake",
"distance": ".5",
"gain": "20",
"level": "easy"
},
{
"trailhead": "Bear Lake",
"trailname": "Nymph Lake",
"distance": ".5",
"gain": "225",
"level": "fairly easy"
}
]
Your trails.json file contains an array with 2 objects, which both represent a single 'Trail'. So you should have a collection 'Trails' instead of a single model
var Trails = Backbone.Collection.extend({
url: '/trails.json'
});
var trails = new Trails();
The underscore template function can be used in 2 ways:
_.template(templateString) - compiles the templateString into function that can be evaluated when necessary
_.template(templateString, data) - compiles and immediately evaluates the template with the given data
Now the way you are using is number 2 (the way you declare the template) combined with number 1 (how you use it inside render). Let's examine the template declaration:
template: _.template($("#trailTemplate").html(), {trails:trail.fetch()})
This is all good up until the point you try to give it the data -attribute. First of all you don't need to give the data at this point, you just want to create the template function that can be evaluated when the View renders. Second, the stuff you are trying to pass as data is not at all what you think it is.
trail.fetch() doesn't return the the fetch results, it returns the ajax handle for the ajax call that is made with fetch. Thankfully Backbone is made so you don't have to think about all this painful ajax stuff, but instead you can trust the events that Backbone emits. So whip out the Backbone Catalog o' Events and check out reset
"reset" (collection, options) — when the collection's entire contents have been replaced.
This is the event you collection will emit, after fetch (also sync, i think). Before this event is emitted, your collection will be empty, so there is no point in doing anything with it before hearing this reset event. So let's bring it all together now:
var TrailView = Backbone.View.extend({
el: '.page',
template: _.template($("#trailTemplate").html()), // no data attribute here
initialize: function() {
this.collection.on('reset', this.render); // render after collection is reset
this.collection.fetch(); // fetch the collection from the trails.json file
}
render: function(){
// evaluate the template here
this.$el.html(this.template(this.collection.toJSON()));
return this;
}
});
var trailView = new TrailView({
collection: trails
});
// trailView.render(); <- No need for this, because it will render itself
Hope this helps!