I am trying ionic2 beta9 and am trying to create a custom component with ion-slide, but I am getting an error.
Parent Component
<ion-content class="home" padding>
<ion-slides loop="true">
<slide-item *ngFor="let image of [1,2,3,4,5]" [imgIdx]="image"></slide-item>
</ion-slides>
</ion-content>
slide-item Custom Component
<ion-slide>
<div class="bcontent">
<div class="bimg">
<img data-src="images/slide{{imgIdx}}.jpeg">
</div>
<p class="info">My text</p>
</div>
</ion-slide>
slide-item.ts
#Component({
selector: 'slide-item',
templateUrl: 'build/components/slide-item/slide-item.html',
directives: [Slides, Slide]
})
export class SlideItem {
#Input()
imgIdx: number;
constructor() {
console.log("SlideItem::constructor...imgIdx="+this.imgIdx);
}
}
I am getting the following error:
zone.js:461
Unhandled Promise rejection: Template parse errors:
No provider for Slides ("
for more info on Angular 2 Components.
-->
[ERROR ->]<ion-slide>
<div class="bcontent">
<div class="bimg">
"): SlideItem#6:0
; Zone: angular ; Task: Promise.then ; Value:
BaseException {message: "Template parse errors:↵No provider for Slides ("↵ …↵ <div class="bimg">↵"):
SlideItem#6:0", stack: "Error: Template parse errors:↵No provider for Slid…ndroid_asset/www/build/js/app.bundle.js:30622:41)"}message:
"Template parse errors:↵No provider for Slides ("↵ for more info on Angular 2 Components.↵-->↵[ERROR ->]<ion-slide>↵ <div class="bcontent">↵ <div class="bimg">↵"): SlideItem#6:0"
stack: "Error: Template parse errors:↵No provider for Slides ("↵ for more info on Angular 2 Components.↵-->↵[ERROR ->]<ion-slide>↵ <div class="bcontent">↵ <div class="bimg">↵"): SlideItem#6:0↵ at new BaseException (file:///android_asset/www/build/js/app.bundle.js:1760:23)↵ at TemplateParser.parse (file:///android_asset/www/build/js/app.bundle.js:16401:19)↵ at file:///android_asset/www/build/js/app.bundle.js:14643:64↵ at ZoneDelegate.invoke (file:///android_asset/www/build/js/zone.js:323:29)↵ at Object.onInvoke (file:///android_asset/www/build/js/app.bundle.js:30631:41)↵ at ZoneDelegate.invoke (file:///android_asset/www/build/js/zone.js:322:35)↵ at Zone.run (file:///android_asset/www/build/js/zone.js:216:44)↵ at file:///android_asset/www/build/js/zone.js:571:58↵ at ZoneDelegate.invokeTask (file:///android_asset/www/build/js/zone.js:356:38)↵ at Object.onInvokeTask (file:///android_asset/www/build/js/app.bundle.js:30622:41)"__proto__: ErrorconsoleError # zone.js:461_loop_1 # zone.js:490drainMicroTaskQueue # zone.js:494ZoneTask.invoke # zone.js:426
Why it doesn't work
I don't think it is possible with the current design of the Slide and Slides components. An ion-slide must be a child of an ion-slides element and it must be able to find its parent Slides within the same template.
Here's the source:
#Component({
selector: 'ion-slide',
template: '<div class="slide-zoom"><ng-content></ng-content></div>',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class Slide {
ele: HTMLElement;
#Input() zoom: any;
constructor(
elementRef: ElementRef,
#Host() public slides: Slides
) {
this.ele = elementRef.nativeElement;
this.ele.classList.add('swiper-slide');
slides.rapidUpdate();
}
ngOnDestroy() {
this.slides.rapidUpdate();
}
}
You can see that the parent Slides is required so that a Slide can trigger the Slides to update when one is dynamically added or destroyed. It is restricted to the current template with the #Host decorator.
You get a template error when you try to use ion-slide in your custom template because there is no Slides object within the template to be passed to the Slide.
One Partial-Solution
I played around with this and you can get it to work like this:
Remove the reference slides to the parent Slides object, sacrificing the ability to automatically update the Slides when you dynamically add/remove slides
Add the "slide-zoom" class to your custom slide.
The modified source for slides.ts:
#Component({
selector: 'ion-slide',
template: '<div class="slide-zoom"><ng-content></ng-content></div>',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class Slide {
ele: HTMLElement;
#Input() zoom: any;
constructor(
elementRef: ElementRef
) {
this.ele = elementRef.nativeElement;
this.ele.classList.add('swiper-slide');
}
}
And your slide item:
#Component({
selector: 'slide-item',
templateUrl: 'slide-item.html',
})
export class SlideItem {
#Input()
imgIdx: number;
constructor(elementRef: ElementRef) {
console.log("SlideItem::constructor...imgIdx="+this.imgIdx);
elementRef.nativeElement.classList.add('swiper-slide');
}
}
I say that this is a partial solution because you have to modify the ionic source and because now you have to manually call update the Slides update when you add/remove slides.
I did make some progress further modifying the ionic source to maintain the automatic update on add/remove slides including custom slide containers such as yours. But it just wasn't looking worth it compared to the option(s) below.
Partial Solution 2
The code for a Slide is really quite simple. You're best bet is probably just to define your own component directly (rather than wrapping ion-slide):
#Component({
selector: 'slide-item',
templateURL: 'slite-item.html',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class SlideItem {
ele: HTMLElement;
#Input() zoom: any;
#Input() imgIdx: number;
constructor(
elementRef: ElementRef
) {
this.ele = elementRef.nativeElement;
this.ele.classList.add('swiper-slide');
console.log("SlideItem::constructor...imgIdx="+this.imgIdx)
}
}
and template
<div class="slide-zoom bcontent">
<div class="bimg">
<img data-src="images/slide{{imgIdx}}.jpeg">
</div>
<p class="info">My text</p>
</div>
I'm calling this one a partial solution also because you have to maintain some extra source that mirrors Ionic. If the ionic slides implementation changes, you may have to update your custom slide.
Improved Solution
Here is a SO answer about extending a component. I haven't gotten this to work because as of now it isn't up to date with Angular (ComponentMetaData no longer exists), but I think it is a nicer approach. You will use this decorator to import and extend the Slide component.
export function CustomComponent(annotation: any) {
return function (target: Function) {
var parentTarget = Object.getPrototypeOf(target.prototype).constructor;
var parentAnnotations = Reflect.getMetadata('annotations', parentTarget);
var parentAnnotation = parentAnnotations[0];
Object.keys(parentAnnotation).forEach(key => {
if (isPresent(parentAnnotation[key])) {
annotation[key] = parentAnnotation[key];
}
});
var metadata = new ComponentMetadata(annotation);
Reflect.defineMetadata('annotations', [ metadata ], target);
}
}
Your template will be the same as the previous solution, but the class is simplified to something like the following.
import { Slide } from 'ionic-angular';
#CustomComponent({
selector: 'slide-item',
templateURL: 'slite-item.html'
})
export class SlideItem extends Slide {
#Input() imgIdx: number;
}
Related
I have this structure for a Vue app:
App.vue
-> Router View(with two child components)
App.vue
<template>
<div id="app" #click="mutateStore(null)">
<router-view #storeUpdate="mutateStore"/>
</div>
</template>
<script>
export default {
name: 'app',
methods:{
mutateStore(){
this.$store.commit('increment')
}
},
mounted(){
this.$store.commit('increment')
}
}
</script>
<style>
...styles
</style>
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
type: 1
},
mutations: {
increment (state) {
state.type++
}
}
})
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Main from '#/components/Childcomponent1'
import Display from '#/components/Childcomponent2'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Childcomponent1',
component: Main
},
{
path: '/display',
name: 'Childcomponent2',
component: Display
}
]
})
In Child Component 1, I have a button where if clicked would do:
this.$emit("storeUpdate")
which triggers the event handler mutateStore() in App.vue
In Vue Dev Tools, it also shows the state.type with the incremented value
In Child Component 2, I display directly the state.type value as computed:
<template>
<div class="main">
{{type}}
</div>
</template>
<script>
export default {
name: 'Childcomponent2',
computed: {
type () {
return this.$store.state.type
}
}
}
</script>
<style scoped>
..Styles
</style>
But the value never got updated in Child Component 2, not even when viewed in Vue Dev Tools.
And one curious observation, when the same commit in App.vue is called in mounted(), it increments the state.type all across the two child components, but when via method mutateStore(), it does update in Child Component 1, but the change is not detected in Child Component 2.
Note that before I did the emit/event handler part in the App.vue, I already tried mutating the store directly from within the Child Component but to no effect that's why I tried the emit event handler instead.
Did I do anything incorrectly?
Please help!
Found it. Turns out I had wrongly assumed that Vuex standard has built in support for shared states across multiple browser windows using localStorage. Apparently it only does share state across multiple components in the SAME browser tab/window. To allow for multiple browser support, a plugin must be added: vuex-shared-mutations
npm install vuex-shared-mutations
I am using Vue.js 2.0, and I have this exact same code in 2 differents files, the only thing that changes is the ID_SPECIFIC_TO_THIS_BLOCK so I am a beginner about Vue and I was wondering if there was an way to implement a kind of template that I would be able to reuse for my 2 files
You can find bellow the entire code for one file:
<template>
<div>
<div class="container">
<hp-row>
<hp-column>
<component v-for="component in content.column" :data="component" :key="component.id" :is="getComponentIdentifier(component.is)"></component>
</hp-column>
</hp-row>
</div>
</div>
</template>
<script>
import ModularView from '#/views/ModularView'
export default {
name: 'AboutUsView',
mixins: [ModularView],
created () {
this.fetch('blocks/ID_SPECIFIC_TO_THIS_BLOCK')
},
}
</script>
Use props:
export default {
name: 'AboutUsView',
mixins: [ModularView],
props: ['ID_SPECIFIC_TO_THIS_BLOCK']
created () {
this.fetch(`blocks/${this.ID_SPECIFIC_TO_THIS_BLOCK}`)
},
}
<about-us-view ID_SPECIFIC_TO_THIS_BLOCK="123"></about-us-view>
<about-us-view ID_SPECIFIC_TO_THIS_BLOCK="789"></about-us-view>
I'm having an issue with Vue. At this point I've read things like this detailing this error occurring when you try to define a method on the root instance, then reference it in local scope.
My issue is slightly different because it is defined in local scope, so I'm not sure what to make of this error. I've also looked at this, and this.
Here is my App.vue:
<template>
<div id="app">
<router-link to="/Home">home</router-link>
<router-link to="/Login">login</router-link>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
console.log('app.vue loading')
</script>
My main.js:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import store from './store'
import router from './router'
import Home from './components/Home'
import Login from './components/Login'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App, Home, Login },
template: '<App/>'
})
console.log('main js loading');
The component the issue is coming from:
<template>
<div class="login">
<head>
<title>{{title}}</title>
</head>
<form for="login" id="loginMain">
<label for="username">Username:</label>
<input type="text" id="username"></input>
<label for="password">Password:</label>
<input type="password"></input><br/><br/>
<button for="login" #click="processLogin">LOGIN</button>
<button for="logout" #click="processLogout">LOGOUT</button>
</form>
<p> Your login status: {{ loginStatus }} </login></p>
</div>
</template>
<script>
import Vue from 'vue'
import { mapGetters, mapActions, Vuex } from 'vuex'
import store from '#/store'
const Login = {
delimiters: [ '[{','}]'],
data () {
title: 'Foo',
msg: 'Bar'
}
name: 'Login',
props: {
// worry about this later
},
methods: {
...mapActions({
processLogin : 'LOGIN_ACTION',
processLogout : 'LOGOUT_ACTION'
})
},
computed: {
...mapGetters({
title: 'GET_TITLE',
loginStatus: 'GET_LOGIN_STATUS'
}),
}
}
console.log('Login loading');
export default Login
And although I'm unsure if it is related, but my store:
import Vue from 'vue'
import Vuex from 'vuex'
import Home from './components/Home'
import Login from './components/Login'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
title: 'My Custom Title',
loggedIn: false
},
mutations: {
MUT_LOGIN: (state) => {
state.loggedIn = true
},
MUT_LOGOUT: (state) => {
state.loggedIn = false
}
},
actions: {
LOGIN_ACTION ({ commit }){
store.commit('MUT_LOGIN')
},
LOGOUT_ACTION ({ commit, state }) {
store.commit('MUT_LOGOUT')
}
},
getters: {
GET_LOGIN_STATUS: state => {
return state.loggedIn
},
GET_TITLE: state => {
return state.title
}
}
})
console.log('store loading');
export default store
I know that I had some of these errors at one point and got rid of them by revisiting each import statement and making some corrections to how I had things hooked up. The only other difference between now and then is that I am running all of these files through Webpack and serving them with Django in a template. (The reason for all the consoles, making sure all the files got in there via Webpack.)
The image below is a couple of the specific things it is barking about.
Because of the link from the devtools error, I have looked at this and also experimented with adding local data () properties to the component itself.
Changing
data () {
title: 'Foo',
msg: 'Bar',
},
to
data () {
return {
title: 'Foo',
msg: 'Bar',
}
},
will fix the "title" error.
As for the issue with the actions nothing is jumping out at me. Can you try mapGetters and see if you have access to the getters?
ion-tabs and ion-content overlaps ion-header when i load an image in the header, it only happens the first time i'm visiting the page, after that it loads well.
this is the component
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'app/home.page.html'
})
export class HomePage {
public image: string;
appName = 'Ionic App';
constructor(public navController: NavController) { }
ionViewDidLoad(){
this.image = "https://source.unsplash.com/640x300/?technology"
}
}
Here is the html
<ion-header>
<ion-navbar>
<ion-title></ion-title>
</ion-navbar>
<img class="cabecera" [src]="image" width="100%">
</ion-header>
<ion-content padding class="contenido">
Welcome to this <ion-icon name="ionic"></ion-icon> <b>Ionic 2 app</b>.
</ion-content>
Its posible to view an example in this plunker
https://plnkr.co/edit/CpBXmn?p=preview
The problem is that you are only loading the image (in the ionViewDidLoad function) after the rest of the content is loaded. After loading the image you need to resize the content, as described in this issue.
Add ViewChild and Content to your imports from #angular/core and ionic-angular, respectively:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
Use #ViewChild to select the class for the ion-content component:
// ...
export class HomePage {
#ViewChild(Content) content: Content;
// ...
Then, once the image is loaded, call the resize method on content:
this.content.resize();
Is there a way to set css properties by using emberjs properties' auto binding ?
Something like:
<div {{bindAttr style="background-color: divColor;"}}>
...
</div>
Please note that as of ember 1.13. binding attributes (bind-attr) is deprecated. You would need to use code similar to this to bind to the class:
<div class={{myClass}}></div>
Furthermore, style binding in this fashion is not recommended because it can introduce XSS vulnerabilities. The HTML templating automatically escapes HTML to prevent XSS when using {{...}}, but style attributes have additional vulnerabilities outside of the scope of the built-in escaping.
The recommended approach is to escape the CSS yourself (i.e. creating the escapeCSS function that would escape the specific CSS appropriately to prevent XSS - this is not a built-in function. You could start with Ember.Handlebars.Utils.escapeExpression and add any additional checking from that base.) More information can be found here:
https://guides.emberjs.com/v2.2.0/templates/writing-helpers/#toc_escaping-html-content
Then you tell Ember that the string is "safe" by using Ember.String.htmlSafe, and Ember will not try to escape that content.
controller:
myStyle: Ember.computed('color', function() {
var color = escapeCSS(this.get('color'));
return new Ember.String.htmlSafe("color: " + color);
})
template:
<div style={{myStyle}}></div>
Reference: http://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes
Another simple way to do this is to add a computed property to your model.
Model ----
App.Photo = Em.Object.extend(
objectId: null
url: ""
style: (->
"background-image:url('" + #get("url") + "')"
).property("url")
)
Template -----
{{#each item in App.photoController}}
<div {{bindAttr style="item.style"}}></div>
{{/each}}
I got this working, and seems to be the simplest way to go about it.
Not exactly like that but close. You'll have to build the style string yourself. Look at this jsFiddle.
App = Ember.Application.create();
/**************************
* Models
**************************/
/**************************
* Views
**************************/
App.View = Ember.View.extend({
style: function() {
return "background-color:" + this.get('color');
}.property('color').cacheable()
});
/**************************
* Controllers
**************************/
App.set('controller', Ember.Object.create({
color: "transparent",
red: function() {
this.set('color', 'red');
},
blue: function() {
this.set('color', 'blue');
},
style: function() {
return "background-color:" + this.get('color');
}.property('color').cacheable()
}));
/**************************
* App Logic
**************************/
$(function() {
template:
{{#view Ember.Button target="App.controller" action="blue"}}BLUE{{/view}}
{{#view Ember.Button target="App.controller" action="red"}}RED{{/view}}
{{#view App.View colorBinding="App.controller.color" attributeBindings="style"}}
Color is {{App.controller.color}}
{{/view}}
<hr>
<div {{bindAttr style="App.controller.style"}}>And another way...</div>
Recent Ember version (2.3.0 as of this writing) allows straight-forward embedding of computed style.
// bar-graph.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['bar-graph'],
inlineStyle: Ember.computed('attrs.min', 'attrs.max', 'attrs.value', function() {
let min = this.get('attrs.min');
let max = this.get('attrs.max');
let value = this.get('attrs.value')
let percentage = Math.round(value / (max - min) * 100);
return new Ember.Handlebars.SafeString(`width: ${percentage}%`);
})
});
<!-- bar-graph.hbs -->
<div class="bar" style={{inlineStyle}}>{{value}}</div>
Live example
I have tried using answer provided by #WallMobile, but it has some syntax issues. So this is the correct syntax to be used.
App.Photo = Em.Object.extend({
objectId: null,
url: "",
style: function() {
return "background-image:url('" + this.get("url") + "')"
}.property("url")
})
HTMLBars now lets you do this - upgrade to the latest ember/ember-cli to take advantage of the new stuff.
There's a new addon which allows you to declare styles as JS objects and bind them to your component's style attribute. Check out ember-computed-style
import computedStyle from 'ember-computed-style';
export default Ember.Component.extend({
style: computedStyle('backgroundStyle'),
attributeBindings: ['style'],
backgroundStyle: function(){
return {
backgroundColor: this.get('divColor')
};
}.property('divColor'),
divColor: 'red'
});
This will produce:
<div style="background-color:red;"></div>
Another approach you could use is CSS custom properties.
ember-cli-custom-properties is an Ember add-on that binds component properties to CSS custom properties (variables). Its fairly simple to use. Once you install the add-on, the add-on makes the customProperties and customPropertyBindings property available on the #ember/component class.
For example, you could turn the raw HTML above into a Ember component, and give it a class name.
import Component from '#ember/component';
export default Component.extend ({
classNames: ['my-component'],
// Map the backgroundColor attribute to a CSS custom property
customProperties: ['backgroundColor']
});
You can then reference this class name in the styles in for your application.
.my-component {
background-color: var(--background-color);
}
Lastly, just set the backgroundColor attribute on the component to your desired color.
{{my-component backgroundColor="red"}}