Syncing a paper-input with firebase using polymer - firebase

How is this for a solution for syning the data from a paper input to a firebase database.
properties: {
teamid: {
type: String,
value: null
},
formid: {
type: String,
value: null
},
metaName: {
type: String,
value: null,
observer: '_updateMetaName'
}
},
_updateMetaName: function(metaName) {
var path = 'formModel/' + this.teamid + '/' + this.formid + '/meta/name';
firebase.database().ref(path).set(metaName);
},
The data metaName comes from a a paper-input element
<paper-input value="{{metaName}}"></paper-input>
I'm using an observer over the on-change attribute because I hate the idea that a user must move out of an input for it to persist.
I've also chosen not to use PolymerFire because i dosen't have some features I need and its not production ready.
I also don't like the idea that the observer runs multiple times before any data has been changed. And that should, i thought, break it but its working to my surprise.
What other options do I have?
Are their any disadvantages to my current solution?

One disadvantage is that every keystroke fires off a request to Firebase, which could be inefficient (a waste of CPU and bandwidth).
To address this, you could debounce the callback with this.debounce(jobName, callback, wait), as shown in the following demo.
HTMLImports.whenReady(_ => {
"use strict";
Polymer({
is: 'x-foo',
properties : {
metaName: {
type: String,
value: 'Hello world!',
observer: '_metaNameChanged'
}
},
_setFirebaseMetaName: function(metaName) {
var path = 'formModel/' + this.teamid + '/' + this.formid + '/meta/name';
//firebase.database().ref(path).set(metaName);
console.log('metaName', metaName);
},
_metaNameChanged: function(metaName) {
this.debounce('keyDebouncer',
_ => this._setFirebaseMetaName(metaName),
500);
}
});
});
<head>
<base href="https://polygit.org/polymer+1.5.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="paper-input/paper-input.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<paper-input label="Meta Name" value="{{metaName}}"></paper-input>
</template>
</dom-module>
</body>
codepen

I've decided to go with on-keyup="_updateViewDesc" to stop a error occurring when multiple clients have the same page open. Using observers, when some data updates, it triggers the observer on all the connected clients. Causing characters to go missing.

Related

Could someone check my PayPal Smart Button?

I'd be grateful if some kind person would glance over this PayPal SmartButton code?
I've put in the NO_SHIPPING and I'm not sure about all the brackets (){}[] and whether there should be double " or single ' inverted commas etc.
I'm OK with html, but this scripting mystifies me.
Thanks in anticipation, Steve
<div id="smart-button-container">
<div style="text-align: center;">
<div id="paypal-button-container"></div>
</div>
</div>
<script src="https://www.paypal.com/sdk/js?client-id=sb&e nablefunding=venmo&currency=GBP" data-sdk-integration-source="button- factory"></script>
<script>
function initPayPalButton() {
paypal.Buttons({
style: {
shape: 'pill',
color: 'gold',
layout: 'vertical',
label: 'buynow',
},
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{"description":"item for sale\nacceptM/accept43_BB1frT6.htm","amount":{"currency_code":"GBP","value":20}}],
application_context: {
shipping_preference: 'NO_SHIPPING'
}
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
// Full available details
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
// Show a success message within this page, e.g.
const element = document.getElementById('paypal-button-container');
element.innerHTML = '';
element.innerHTML = '<h3>Thank you for your payment!</h3>';
//actions.redirect('https://www.website.com/');
});
},
onError: function(err) {
console.log(err);
}
}).render('#paypal-button-container');
}
initPayPalButton();
</script>
Script SDK line is not correct, has extra spacing and a missing hyphen. You need:
<script src="https://www.paypal.com/sdk/js?client-id=sb&enable-funding=venmo&currency=GBP" data-sdk-integration-source="button-factory"></script>
That's simply the code the button factory would have generated for you, and it works.
For future reference most HTML/JS problems can be troubleshooted in a browser's Developer Tools, on the Console and Network and (for HTML) Inspect tabs, reloading the page once the Network tab is open for example.

Vue/Vuex watcher dynamic/async component loading

I have a base component within which I have a dynamic component with a v-for that displays based on a computed property.
All I've really tried doing thus far, which was an incorrect methodology, was to wrap the method that loads data in a settimeout. This question is as much a methodology question as it is a coding question.
My base component looks like this:
<template>
<div>
<v-progress-linear
v-model="progressValue"
v-if="loading"
></v-progress-linear>
<component
v-for="table in tables"
:key="table.id"
:is="table.structure"
:table="table"
></component>
</div>
</template>
<script>
import Annual from './DataTables/Annual';
import { mapState, mapGetters } from 'vuex';
export default {
name: "Page",
props: [],
components: {
Annual,
},
data: () => ({
progressValue: 0,
loading: false,
tables: [],
}),
computed: {
...mapGetters({
currentTables: 'getCurrentPageTables',
tableTitles: 'getCurrentPageTableTitles',
}),
...mapState({
pageName: state => state.pageName,
snakeName: state => state.snakeName,
}),
methods: {
updateTables(payload) {
this.loading = true;
payload.forEach(title => {
this.tables.push(this.currentTables.filter(e => title === e.name)[0]);
this.progressValue = this.tables.length / payload.length;
})
},
},
watch: {
snakeName: {
handler() {
this.progressValue = 0;
this.updateTables(this.tableTitles);
this.$nextTick(() => {this.loading = false;})
},
immediate: true,
},
}
}
</script>
Annual.vue is simply a component that displays a Vuetify v-data-table element and its structure is fairly inconsequential to this.
For all intents and purposes we can consider currentTables and tableTitles to both be arrays, the first of objects whose data populate the v-data-tables in Annual.vue, and the second of strings which are just the names of the tables.
When the user navigates to another page the getters return different data, based on the page the user navigates to, but some of the pages have over 20 tables, which makes page loading slow upon navigation to these pages. I am trying to do one of two things:
1. Asynchronously load the components one at a time while still making the page functional for the user to navigate through.
2. Display a loader that disappears after all of the content is rendered. I'm having trouble figuring out how to do the latter because I can't put this functionality into the mounted() hook since all of this happens upon the watched parameter changing (hence the component is not re-mounted each time the route changes).
Any advice on how to tackle this would be appreciated.

Polymer: detecting an object property change from the parent component

I have a component that loads an object from Firebase using firebase-document. Then that object is passed to a child component. When I change the object in the child component, the change is not detected in the parent, so the object is not updated in Firebase.
Here is the main component:
<dom-module id="some-component">
<template>
<firebase-document path="/projects/[[project_id]]" data="{{project}}"></firebase-document>
<some-child project="{{project}}"></some-child>
</template>
<script>
Polymer({
is: 'some-component',
properties: {
project: {type: Object, notify: true, observer: "projectChanged"}
},
projectChanged: function() {
console.log("we've detected some changes!");
}
});
</script>
</dom-module>
And here is the child component:
<dom-module id="some-child">
<template>
<a on-tap="changeProject">Let's change some property on our project!</a>
</template>
<script>
Polymer({
is: 'some-child',
properties: {
project: {type: Object, notify: true}
},
changeProject: function() {
this.project.name = "A new name"; // this never propagates back to the parent component
}
});
</script>
</dom-module>
The expected behavior is that when I click on the link, the object's property would change, it would be detected by the parent, and there would be a console.log. However it doesn't seem to happen.
Update with the solution
Using this.set() in the child component does the trick:
this.set("project.name", "A new name")
I think your issue here, is from the observer.
A simple observer like this only watch the reference of the object itself.
You can use a deep observer instead like this
properties: {
project: {type: Object, notify: true}
},
observers:[
'projectChanged(project.name)'
],
or for a more properties generic version
properties: {
project: {type: Object, notify: true}
},
observers:[
'projectChanged(project.*)'
],
and it should work like this.
Here is the link to the full documentation on the subject.
https://www.polymer-project.org/1.0/docs/devguide/observers#observing-path-changes
To be perfect you can also change the classic way of setting the value by the polymer way, it ensure better detection by the framework, remplacing
this.project.name = "A new name";
by
this.set("project.name", "A new name")
It will help on complexe binding case, or heavy objects it seems.

Polymer communication between elements

I want to achieve communication between child parent with Polymer element.
Here my index.html
<proto-receiver data="message">
<proto-element data="message"></proto-element>
</proto-receiver>
Both element have their respective "data" property
properties: {
data: {
value: 'my-data',
notify: true,
}
},
In proto-receiver, which is the parent I update "data" by handling simple click
<template>
<span on-tap="onClick">proto receiver: {{data}}</span>
<content></content>
</template>
onClick: function () {
this.data = 'new-message';
},
I want the change to be propagate to the child element as well, as it mentioned here.
I achieve this by passing a setter in my child element and called it like this. Which is, I guess, not the way it should be done.
Polymer.Base.$$('body').querySelector('proto-element').setData(this.data);
What I'm doing wrong
Thanks
UPDATE:
For those coming here. The proper way of doing this is by using Events.
Polymer 1.x
this.fire('kick', {kicked: true});
Polymer 2.x (simple javascript)
this.dispatchEvent(new CustomEvent('kick', {detail: {kicked: true}}));
In both case the receiver should implement the regular addEventListener
document.querySelector('x-custom').addEventListener('kick', function (e) {
console.log(e.detail.kicked); // true
})
To provide a concrete example to Scott Miles' comments, if you can wrap your parent and child elements in a Polymer template (such as dom-bind or as children to yet another Polymer element), then you can handle this declaratively. Check out the mediator pattern.
parent element:
<dom-module id="parent-el">
<template>
<button on-tap="onTap">set message from parent-el</button>
<p>parent-el.message: {{message}}</p>
<content></content>
</template>
<script>
Polymer({
is: 'parent-el',
properties: {
message: {
type: String,
notify: true
}
},
onTap: function() {
this.message = 'this was set from parent-el';
}
});
</script>
</dom-module>
child element:
<dom-module id="child-el">
<template>
<p>child-el.message: {{message}}</p>
</template>
<script>
Polymer({
is: 'child-el',
properties: {
message: {
type: String,
notify: true
}
}
});
</script>
</dom-module>
index.html:
<template is="dom-bind" id="app">
<parent-el message="{{message}}">
<child-el message="{{message}}"></child-el>
</parent-el>
</template>
<script>
(function(document) {
var app = document.querySelector('#app');
app.message = 'this was set from index.html script';
}) (document);
</script>
JS Bin
I was facing same issue and got solution for it and fixed it as below
this.fire('iron-signal', {name: 'hello', data: null});
You can refer this iron-signals you will get the solution which you are looking for its basically event fire from any element to another
Hope this will help you
Polymer iron signals

How to include view/partial specific styling in AngularJS?

What is the proper/accepted way to use separate stylesheets for the various views my application uses?
Currently I'm placing a link element in the view/partial's html at the top but I've been told this is bad practice even though all modern browsers support it but I can see why it's frowned upon.
The other possibility is placing the separate stylesheets in my index.html's head but I would like it to only load the stylesheet if its view is being loaded in the name of performance.
Is this bad practice since styling won't take effect until after the css is loaded form the server, leading to a quick flash of unformatted content in a slow browser? I have yet to witness this although I'm testing it locally.
Is there a way to load the CSS through the object passed to Angular's $routeProvider.when?
I know this question is old now, but after doing a ton of research on various solutions to this problem, I think I may have come up with a better solution.
UPDATE 1: Since posting this answer, I have added all of this code to a simple service that I have posted to GitHub. The repo is located here. Feel free to check it out for more info.
UPDATE 2: This answer is great if all you need is a lightweight solution for pulling in stylesheets for your routes. If you want a more complete solution for managing on-demand stylesheets throughout your application, you may want to checkout Door3's AngularCSS project. It provides much more fine-grained functionality.
In case anyone in the future is interested, here's what I came up with:
1. Create a custom directive for the <head> element:
app.directive('head', ['$rootScope','$compile',
function($rootScope, $compile){
return {
restrict: 'E',
link: function(scope, elem){
var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
elem.append($compile(html)(scope));
scope.routeStyles = {};
$rootScope.$on('$routeChangeStart', function (e, next, current) {
if(current && current.$$route && current.$$route.css){
if(!angular.isArray(current.$$route.css)){
current.$$route.css = [current.$$route.css];
}
angular.forEach(current.$$route.css, function(sheet){
delete scope.routeStyles[sheet];
});
}
if(next && next.$$route && next.$$route.css){
if(!angular.isArray(next.$$route.css)){
next.$$route.css = [next.$$route.css];
}
angular.forEach(next.$$route.css, function(sheet){
scope.routeStyles[sheet] = sheet;
});
}
});
}
};
}
]);
This directive does the following things:
It compiles (using $compile) an html string that creates a set of <link /> tags for every item in the scope.routeStyles object using ng-repeat and ng-href.
It appends that compiled set of <link /> elements to the <head> tag.
It then uses the $rootScope to listen for '$routeChangeStart' events. For every '$routeChangeStart' event, it grabs the "current" $$route object (the route that the user is about to leave) and removes its partial-specific css file(s) from the <head> tag. It also grabs the "next" $$route object (the route that the user is about to go to) and adds any of its partial-specific css file(s) to the <head> tag.
And the ng-repeat part of the compiled <link /> tag handles all of the adding and removing of the page-specific stylesheets based on what gets added to or removed from the scope.routeStyles object.
Note: this requires that your ng-app attribute is on the <html> element, not on <body> or anything inside of <html>.
2. Specify which stylesheets belong to which routes using the $routeProvider:
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/some/route/1', {
templateUrl: 'partials/partial1.html',
controller: 'Partial1Ctrl',
css: 'css/partial1.css'
})
.when('/some/route/2', {
templateUrl: 'partials/partial2.html',
controller: 'Partial2Ctrl'
})
.when('/some/route/3', {
templateUrl: 'partials/partial3.html',
controller: 'Partial3Ctrl',
css: ['css/partial3_1.css','css/partial3_2.css']
})
}]);
This config adds a custom css property to the object that is used to setup each page's route. That object gets passed to each '$routeChangeStart' event as .$$route. So when listening to the '$routeChangeStart' event, we can grab the css property that we specified and append/remove those <link /> tags as needed. Note that specifying a css property on the route is completely optional, as it was omitted from the '/some/route/2' example. If the route doesn't have a css property, the <head> directive will simply do nothing for that route. Note also that you can even have multiple page-specific stylesheets per route, as in the '/some/route/3' example above, where the css property is an array of relative paths to the stylesheets needed for that route.
3. You're done
Those two things setup everything that was needed and it does it, in my opinion, with the cleanest code possible.
#tennisgent's solution is great. However, I think is a little limited.
Modularity and Encapsulation in Angular goes beyond routes. Based on the way the web is moving towards component-based development, it is important to apply this in directives as well.
As you already know, in Angular we can include templates (structure) and controllers (behavior) in pages and components. AngularCSS enables the last missing piece: attaching stylesheets (presentation).
For a full solution I suggest using AngularCSS.
Supports Angular's ngRoute, UI Router, directives, controllers and services.
Doesn't required to have ng-app in the <html> tag. This is important when you have multiple apps running on the same page
You can customize where the stylesheets are injected: head, body, custom selector, etc...
Supports preloading, persisting and cache busting
Supports media queries and optimizes page load via matchMedia API
https://github.com/door3/angular-css
Here are some examples:
Routes
$routeProvider
.when('/page1', {
templateUrl: 'page1/page1.html',
controller: 'page1Ctrl',
/* Now you can bind css to routes */
css: 'page1/page1.css'
})
.when('/page2', {
templateUrl: 'page2/page2.html',
controller: 'page2Ctrl',
/* You can also enable features like bust cache, persist and preload */
css: {
href: 'page2/page2.css',
bustCache: true
}
})
.when('/page3', {
templateUrl: 'page3/page3.html',
controller: 'page3Ctrl',
/* This is how you can include multiple stylesheets */
css: ['page3/page3.css','page3/page3-2.css']
})
.when('/page4', {
templateUrl: 'page4/page4.html',
controller: 'page4Ctrl',
css: [
{
href: 'page4/page4.css',
persist: true
}, {
href: 'page4/page4.mobile.css',
/* Media Query support via window.matchMedia API
* This will only add the stylesheet if the breakpoint matches */
media: 'screen and (max-width : 768px)'
}, {
href: 'page4/page4.print.css',
media: 'print'
}
]
});
Directives
myApp.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: 'my-directive/my-directive.html',
css: 'my-directive/my-directive.css'
}
});
Additionally, you can use the $css service for edge cases:
myApp.controller('pageCtrl', function ($scope, $css) {
// Binds stylesheet(s) to scope create/destroy events (recommended over add/remove)
$css.bind({
href: 'my-page/my-page.css'
}, $scope);
// Simply add stylesheet(s)
$css.add('my-page/my-page.css');
// Simply remove stylesheet(s)
$css.remove(['my-page/my-page.css','my-page/my-page2.css']);
// Remove all stylesheets
$css.removeAll();
});
You can read more about AngularCSS here:
http://door3.com/insights/introducing-angularcss-css-demand-angularjs
Could append a new stylesheet to head within $routeProvider. For simplicity am using a string but could create new link element also, or create a service for stylesheets
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#myViewName').length){
angular.element('head').append('<link id="myViewName" href="myViewName.css" rel="stylesheet">');
}
Biggest benefit of prelaoding in page is any background images will already exist, and less lieklyhood of FOUC
#sz3, funny enough today I had to do exactly what you were trying to achieve: 'load a specific CSS file only when a user access' a specific page. So I used the solution above.
But I am here to answer your last question: 'where exactly should I put the code. Any ideas?'
You were right including the code into the resolve, but you need to change a bit the format.
Take a look at the code below:
.when('/home', {
title:'Home - ' + siteName,
bodyClass: 'home',
templateUrl: function(params) {
return 'views/home.html';
},
controler: 'homeCtrl',
resolve: {
style : function(){
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#mobile').length){
angular.element('head').append('<link id="home" href="home.css" rel="stylesheet">');
}
}
}
})
I've just tested and it's working fine, it injects the html and it loads my 'home.css' only when I hit the '/home' route.
Full explanation can be found here, but basically resolve: should get an object in the format
{
'key' : string or function()
}
You can name the 'key' anything you like - in my case I called 'style'.
Then for the value you have two options:
If it's a string, then it is an alias for a service.
If it's function, then it is injected and the return value is treated
as the dependency.
The main point here is that the code inside the function is going to be executed before before the controller is instantiated and the $routeChangeSuccess event is fired.
Hope that helps.
Awesome, thank you!! Just had to make a few adjustments to get it working with ui-router:
var app = app || angular.module('app', []);
app.directive('head', ['$rootScope', '$compile', '$state', function ($rootScope, $compile, $state) {
return {
restrict: 'E',
link: function ($scope, elem, attrs, ctrls) {
var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
var el = $compile(html)($scope)
elem.append(el);
$scope.routeStyles = {};
function applyStyles(state, action) {
var sheets = state ? state.css : null;
if (state.parent) {
var parentState = $state.get(state.parent)
applyStyles(parentState, action);
}
if (sheets) {
if (!Array.isArray(sheets)) {
sheets = [sheets];
}
angular.forEach(sheets, function (sheet) {
action(sheet);
});
}
}
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
applyStyles(fromState, function(sheet) {
delete $scope.routeStyles[sheet];
console.log('>> remove >> ', sheet);
});
applyStyles(toState, function(sheet) {
$scope.routeStyles[sheet] = sheet;
console.log('>> add >> ', sheet);
});
});
}
}
}]);
If you only need your CSS to be applied to one specific view, I'm using this handy snippet inside my controller:
$("body").addClass("mystate");
$scope.$on("$destroy", function() {
$("body").removeClass("mystate");
});
This will add a class to my body tag when the state loads, and remove it when the state is destroyed (i.e. someone changes pages). This solves my related problem of only needing CSS to be applied to one state in my application.
'use strict';
angular.module('app')
.run(
[
'$rootScope', '$state', '$stateParams',
function($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]
)
.config(
[
'$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider
.otherwise('/app/dashboard');
$stateProvider
.state('app', {
abstract: true,
url: '/app',
templateUrl: 'views/layout.html'
})
.state('app.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
ncyBreadcrumb: {
label: 'Dashboard',
description: ''
},
resolve: {
deps: [
'$ocLazyLoad',
function($ocLazyLoad) {
return $ocLazyLoad.load({
serie: true,
files: [
'lib/jquery/charts/sparkline/jquery.sparkline.js',
'lib/jquery/charts/easypiechart/jquery.easypiechart.js',
'lib/jquery/charts/flot/jquery.flot.js',
'lib/jquery/charts/flot/jquery.flot.resize.js',
'lib/jquery/charts/flot/jquery.flot.pie.js',
'lib/jquery/charts/flot/jquery.flot.tooltip.js',
'lib/jquery/charts/flot/jquery.flot.orderBars.js',
'app/controllers/dashboard.js',
'app/directives/realtimechart.js'
]
});
}
]
}
})
.state('ram', {
abstract: true,
url: '/ram',
templateUrl: 'views/layout-ram.html'
})
.state('ram.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard-ram.html',
ncyBreadcrumb: {
label: 'test'
},
resolve: {
deps: [
'$ocLazyLoad',
function($ocLazyLoad) {
return $ocLazyLoad.load({
serie: true,
files: [
'lib/jquery/charts/sparkline/jquery.sparkline.js',
'lib/jquery/charts/easypiechart/jquery.easypiechart.js',
'lib/jquery/charts/flot/jquery.flot.js',
'lib/jquery/charts/flot/jquery.flot.resize.js',
'lib/jquery/charts/flot/jquery.flot.pie.js',
'lib/jquery/charts/flot/jquery.flot.tooltip.js',
'lib/jquery/charts/flot/jquery.flot.orderBars.js',
'app/controllers/dashboard.js',
'app/directives/realtimechart.js'
]
});
}
]
}
})
);

Resources