How can component in iron-pages know when it is selected - polymer-1.0

I have a Polymer 1.0 iron-pages element containing two custom elements:
<iron-pages selected="...">
<my-page>A</my-page>
<my-page>B</my-page>
</iron-pages>
I would like to take some action, like fetching ajax content, in my-page component, when the page becomes selected. How can I do this?
I came up with some ideas:
create a third component containing the iron-pages and wiring the events
<dom-module id="my-controller">
<template>
<iron-pages selected="..." on-selected-changed="onPageChanged">
...
<script>Polymer({...
...
onPageChanged:function(){
var page = ...;
page.selected = true;
}
and
<dom-module id="my-page">
...
onSelected:function(){
// fetch data
}
seems being rather much of an overhead to me, is this really necessary?
use <iron-pages selectedAttribute="..." ...>
but I could not find a way to detect the attribute change in <my-page>
Are there common patterns to solve this?

My solution was similar to your option A, but using events instead of method calls - its a more correct approach, and I'm actually baffled why Polymer's ironSelectableBehavior didn't implement it directly:
<iron-pages id="pages" ...>
<my-first-page></my-first-page>
<my-second-page></my-second-page>
</iron-pages>
...
<script>
Polymer({
is: 'my-app',
listeners: {
'pages.iron-select': 'pageSelected',
'pages.iron-deselect': 'pageDeselected'
},
pageSelected: function(e) { e.detail.item.fire('iron-select'); },
pageDeselected: function(e) { e.detail.item.fire('iron-deselect'); })
});
</script>
Then, in your element, listen for these events:
Polymer({
is: 'my-custom-element',
listeners: {
'iron-select': '_refreshData'
},
_refreshData: function(e) {
// load some data
}
});
Option B should also work - you probably need to set up the correct property and put an observer on it. In your component that contains iron-pages set up the attribute using the hyphenated form:
<iron-pages selected-attribute="activated" ...>
<my-first-page></my-first-page>
<my-second-page></my-second-page>
</iron-pages>
Then in your custom component set up the property and the observer:
Polymer({
is: 'my-first-page',
properties: {
activated: {
type: Boolean,
observer: '_activationChanged'
}
},
_activationChanged: function(newval, oldval) {
if (newval) // == true
console.log("just activated");
}
});

<iron-pages selected-attribute="visible"></iron-pages>
class MyClass extends PolymerElement {
get activePage() {
return this.shadowRoot.querySelector('iron-pages > *[visible]');
}
}
(sorry for the zombie thread, but came looking for this).

Related

Angular 1.5 Components + ng-model $formatters and $parsers

I would like to know how to use $formatters and $parsers with angular 1.5 components. Can someone post an example.
Or is there something similar that I can use.
The following is an example of a component called example. This takes in a object that contains firstName and secondName. It then displays a combination of the firstName and secondName. If the object changes from the outside the formatter will fire followed by the render. If you want to trigger a change from the inside, you need to call this.ngModel.$setViewValue(newObject) and this would trigger the parser.
class example {
/*#ngInject*/
constructor() {}
// In the post link we need to add our formatter, parser and render to the ngmodel.
$postLink() {
this.ngModel.$formatters.push(this.$formatter.bind(this));
this.ngModel.$parsers.push(this.$parser.bind(this));
this.ngModel.$render = this.$render.bind(this);
}
// The formatter is used to intercept the model value coming in to the controller
$formatter(modelValue) {
const user = {
name: `${modelValue.firstName} ${modelValue.secondName}`
};
return user;
}
// The parser is used to intercept the view value before it is returned to the original source
// In this case we want to turn it back to it's original structure what ever that may be.
$parser(viewValue) {
// We know from out formatter that our view value will be an object with a name field
const namesParts = viewValue.name.split(' ');
const normalisedUser = {
firstName: namesParts[0],
secondName: namesParts[1],
};
return normalisedUser;
}
// This will fire when ever the model changes. This fires after the formatter.
$render() {
this.displayName = this.ngModel.$viewValue.name;
}
}
class ExampleComponent
{
bindings = {};
controller = Example;
require = {
ngModel: 'ngModel',
};
}
component('example', new ExampleComponent());
// Template for example component
<span>
{{ $ctrl.displayName }}
</span>
// Using the above component somewhere
<example ng-model="userModel"></example>

Select the text inside an input using Typescript in Angular 2

I'm trying to do exactly what is described in this post, but in Angular2.
Basically use the javascript function .setSelectionRange(start, end); in an input after a user clicks on a trigger. I can't find any way to replicate this behaviour using Typescript.
Thanks in advance.
I can't find any way to replicate this behaviour using Typescript.
TypeScript is just JavaScript. I suspect you mean to say Angular2 (that post is Angular1).
Angular2
You need to get a hold of the dom element (which is what you seem to be struggling with). In your controller you need to inject ElementRef. E.g. #Inject(ElementRef) elementRef: ElementRef,
Once you have the element you can traverse it and do whatever dom access / manual manipulation you need to do.
More
Docs : https://angular.io/docs/js/latest/api/core/ElementRef-class.html
Example
Sample : https://stackoverflow.com/a/32709672/390330
import {Component, ElementRef} from 'angular2/core';
#Component({
selector:'display',
template:`
<input #myname (input) = "updateName(myname.value)"/>
<p> My name : {{myName}}</p>
`
})
class DisplayComponent implements OnInit {
constructor(public element: ElementRef) {
this.element.nativeElement // <- your direct element reference
}
ngOnInit() {
}
}
This example line of code shows the essence of selecting the text (with .name being an ElementRef reference):
this.name.nativeElement.setSelectionRange(0, 999);
Here are all the necessary pieces put together (as well as putting focus on the input) for a "name" field:
View:
<input name="name" formControlName="name" type="text" [(ngModel)]="data.name">
Component:
export class MyComponent {
#ViewChild('name') name: ElementRef; // * see security comment below for ElementRef
#Input() data: {name: 'Foo Baz'};
myForm: FormGroup;
constructor() {
this.myForm = new FormGroup({
name: new FormControl()
});
}
// call this to give the field focus and select its text
focusAndSelectNameFieldText(){
if (!this.name) return;
this.name.nativeElement.focus();
setTimeout(() => {
this.name.nativeElement.setSelectionRange(0, 999);
});
}
}
*Please be sure your use of ElementRef does not pose a security risk:
https://stackoverflow.com/a/44509202/442665

Polymer 1.0 observers - not working on array

I set an observer on to catch all polymer recognized events on an property that is an array, but I catch get it to catch the change. In my example below my observer function "bigup" only gets called on when the property, "bigs" is first initialzed.
<dom-module id="parent-page">
<template>
<paper-button on-click="updateAll">Update</paper-button>
</template>
<script>
var temp=[];
temp.push({'conversation':[{'message':'hello'}]});
Polymer({
is: 'parent-page',
properties: {
bigs: {
type: Array,
value: temp
}
},
observers: [
'bigup(bigs.*)'
],
updateAll: function(){
this.bigs.push({'conversation':[{'message':'hola'}]});
console.dir(this.bigs);
},
bigup: function(){
console.log('big Up');
}
});
</script>
I also tried to use bigs.push in the observer but had no success. One part I don't understand is that if I add the following line to my "updateAll" function, the observer catches the change and fires "bigup".
this.bigs=[];
For me this article was helpful as well, it covers both the way to register the observer as well as the splices methods as suggested by Justin XL.
Registering the observer:
properties: {
users: {
type: Array,
value: function() {
return [];
}
}
},
observers: [
'usersAddedOrRemoved(users.splices)'
],
Calling splices methods the Polymer 1.0 way:
this.push('users', 'TestUser');
https://www.polymer-project.org/1.0/docs/devguide/properties.html#array-observation
FYI - this will NOT work in all cases (my initial idea)
When you register the observer in the property declaration like this:
properties: {
users: {
type: Array,
value: function() {
return [];
},
observer: 'usersAddedOrRemoved'
}
},
In this case the usersAddedOrRemoved method is only called when you assign a new array to the users object. It will not however fire when you mutate the array by pushing, popping, splicing etc.
You need to use the push method from Polymer instead of doing this.bigs.push.
So replace that line of code with
this.push('bigs', {'conversation':[{'message':'hola'}]});
For more info have a look at this link.

Easily Catch Enter to Submit Form

Is there an easy way to enable hitting enter to execute some javascript for a form with paper-input's. I can catch the keystroke on enter for every element but this seems kind of tedious.
With the current Polymer version 1.0 I was able to resolve that using iron-a11y-keys.
Here is an example bound to the whole form which triggers submission on any child input element:
<iron-a11y-keys id="a11y" target="[[_form]]" keys="enter"
on-keys-pressed="submitForm"></iron-a11y-keys>
<form is="iron-form" id="form"
method="post"
action="{{url}}">
...
Polymer({
is: 'example-form',
properties: {
_form: {
type: Object,
value: function() {
return this.$.form;
}
}
},
submitForm: function() {
document.getElementById('form').submit();
},
Currently (Polymer 0.3.4) there seems to be no event fired when one presses the enter key in a paper-input. But you can extend the paper-input element and add this functionality (see Extending other elements in the Polymer doc):
<polymer-element name="my-paper-input" extends="paper-input">
<template>
<shadow></shadow>
</template>
...
</polymer-element>
Then you can fire a custom event when the return key is pressed:
ready: function() {
self = this;
this.$.input.addEventListener('keypress', function(e) {
if (e.keyCode == 13) {
self.async(function() {
self.fire('enter', self.value);
});
}
});
}
For convenience the input value is passed to the event handler. Now you can use your new element like so:
<my-paper-input ... on-enter="{{inputEntered}}"></my-paper-input>
Edit 1:
Since the event bubbles up in the element hierarchy, one can catch it on the surrounding form element:
<my-form on-enter="{{anyInputEntered}}" ...>
Then one gets the events of all input elements in one place (the event propagation can be stopped by calling stopPropagation(); on the event object).
Edit 2:
It's best to give custom events unique names, so that they don't clash with the names of core events that may be added in the future (e.g. my-unique-prefix-input-entered).

BackboneJS - same el for many views

I am using same el for more than 1 view like below. I'm not facing any problem till now. Is this good approach or should i do any changes?
<div id="app">
<div id="app-header"></div>
<div id="app-container"></div>
<div id="app-footer">
</div>
App View:
{
el: "#app",
v1: new View1(),
v2: new View2(),
render: function () {
if (cond1) {
this.v1.render();
} else if (cond2) {
this.v2.render();
}}
}
View 1:
{
el: "#app-container",
render: function (){
this.$el.html(template);
}
}
View 2:
{
el: "#app-container",
render: function (){
this.$el.html(template);
}
}
By reading your question, I do not really see what advantages you could possibly have using this approach rather than having the different div elements being the root el for your views 1, 2, 3 and using
this.$el.html(template)
in the render method.
Your approach could work for a small application, but I think it will become really hard to maintain as the application grows.
EDIT
I still do not really get your point, you could only initialize everything only once in both cases.
Here is a working Fiddle.
By the way I am changing the content by listening to the click event but this is to simplify the example. It should be done by the router.
I do use a mixin to handle such situation, I call it stated view. For a view with all other options I will send a parameter called 'state', render will in-turn call renderState first time and there after every time I set a 'state' renderState will update the view state. Here is my mixin code looks like.
var setupStateEvents = function (context) {
var stateConfigs = context.getOption('states');
if (!stateConfigs) {
return;
}
var state;
var statedView;
var cleanUpState = function () {
if (statedView) {
statedView.remove();
}
};
var renderState = function (StateView) {
statedView = util.createView({
View: StateView,
model: context.model,
parentEl: context.$('.state-view'),
parentView:context
});
};
context.setState = function (toState) {
if (typeof toState === 'string') {
if (state === toState) {
return;
}
state = toState;
var StateView = stateConfigs[toState];
if (StateView) {
cleanUpState();
renderState(StateView);
} else {
throw new Error('Invalid State');
}
} else {
throw new Error('state should be a string');
}
};
context.getState = function () {
return state;
};
context.removeReferences(function(){
stateConfigs = null;
state=null;
statedView=null;
context=null;
})
};
full code can be seen here
https://github.com/ravihamsa/baseapp/blob/master/js/base/view.js
hope this helps
Backbone Rule:
When you create an instance of a view, it'll bind all events to el if
it was assigned, else view creates and assigns an empty div as el for that view and bind
all events to that view.
In my case, if i assign #app-container to view 1 and view 2 as el and when i initialize both views like below in App View, all events bind to the same container (i.e #app-container)
this.v1 = new App.View1();
this.v2 = new App.View2();
Will it lead to any memory leaks / Zombies?
No way. No way. Because ultimately you are having only one instance for each view. So this won't cause any memory leaks.
Where does it become problematic?
When your app grows, it is very common to use same id for a tag in both views. For example, you may have button with an id btn-save in both view's template. So when you bind btn-save in both views and when you click button in any one the view, it will trigger both views save method.
See this jsFiddle. This'll explain this case.
Can i use same el for both view?
It is up to you. If you avoid binding events based on same id or class name in both views, you won't have any problem. But you can avoid using same id but it's so complex to avoid same class names in both views.
So for me, it looks #Daniel Perez answer is more promising. So i'm going to use his approach.

Resources