I have a properties component, which contains some input props
props: ['activeObjectProps', 'canvas', 'fonts']
i need to watch activeObjectProps for changes, so here is wathcer
watch: {
'activeObjectProps': {
handler: function (newProps) {
console.log('watch triggered');
},
deep: true
}
}
in template i have following v-for loop:
<div class="col-xs-4 center-xs" v-for="font in fonts" #click="setFont(font)" :style="{fontFamily: font}">
{{font}}
</div>
and finally setFont method
methods: {
setFont: function (font) {
//this.editFontModal.close();
this.$set(this.activeObjectProps, 'fontFamily', font);
}
}
if i understood correctly, i should call $set function to set this reactive parameter, but this is not works. If i click on some font, handler function are not triggered. Of course if i just this.activeObjectProps.fontFamily = font - it doesn't work too. Can you give me an advice, what am i doing wrong?
Related
I'm having trouble figuring out how to render a parent component, display a list of contracts in a list on part of the page, and when a user clicks on one of them, display the details of that specific contract on the other part of the page.
Here is my slim file:
#contracts_area
.filter-section
ul
li.filter-item v-for="contract in contractsAry" :key="contract.id" #click="showContract(contract)"
| {{ contract.name }}
.display-section
component :is="currentView" transition="fade" transition-mode="out-in"
script type="text/x-template" id="manage-contracts-template"
div
h1 Blank when page is newly loaded for now
script type="text/x-template" id="view-contract-template"
div :apply_contract="showContract"
h1#display-item__name v-name="name"
javascript:
Vue.component('manage-template', {
template: '#manage-contracts-template'
});
Vue.component('view-contract', {
template: '#view-contract-template',
props: ['show_contract'],
data: function() {
return {
name: ''
}
},
methods: {
showContract: function(contract) {
return this.name = contract.name
}
}
});
Vue.http.headers.common['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content');
var contractsResource = Vue.resource('/all_contracts{/id}.json');
var contracts = new Vue({
el: '#contracts_area',
data: {
currentView: 'manage-template',
contractsAry: [],
errors: {}
},
mounted: function() {
var that = this;
contractsResource.get().then(
function(res) {
that.contractsAry = res.data;
}
)
},
methods: {
showContract: function(contract) {
this.currentView = 'view-contract'
}
}
});
Basically I'd like it so that when a user clicks on any contract item in the .filter-section, it shows the data for that contract in the .display-section. How can I achieve this?
In short you can bind a value to a prop.
.display-section
component :is="currentView" :contract="currentContract"
view-contract
props: ['contract']
contracts-area
data: {
currentContract: null,
},
methods: {
showContract: function(contract) {
this.currentView = "view-contract";
this.currentContract = contract;
}
}
There are multiple ways to pass data in Vue.
Binding values to props.
Using ref to directly call a method from a child component.
Custom Events. Note that to pass events globally, you will need a global event bus.
A single central source of truth (i.e. vuex)
I have illustrated methods 1, 2, 3 in Codepen
Note that 2nd and 3rd methods will only work after your component has been rendered. In your case, since your components for currentView are dynamic and when user clicked, display-section component does not yet exists; it will not receive any events yet. So their content will be empty at first.
To workaround this you can directly access $parent in mounted() from child component, however this would create coupling between them. Another solution is creating the components but conditionally displaying them. And one another solution would be waiting until child component has been mounted and then emitting events.
If your needs are simple I suggest binding values to props (1), else you may consider using something like vuex.
I have this template:
<template>
<div>
<div v-for="report in reports">
<div class="map" v-bind:id="mapID = report.started.toUpperCase()" v-text="report.started">
{{hello(mapID)}}
</div>
</div>
</div>
and this model:
<script>
import addmap from '../components/addmap';
export default {
data: function(){
return {
reports: [],
mapid: "",
map_id: ''
}
},
mounted: function(){
this.mapID = this.map_id;
this.allReport()
},
components: {
'addmap': addmap
},
computed: {
mapID: {
get: function(){
return this.map_id;
},
set: function(newValue){
this.map_id = newValue.toUpperCase();
}
}
},
methods: {
hello: function(val){
console.log(val)
},
allReport: function(){
axios.get('/reports').then(response => {
this.reports = response.data
});
}
}
}
Whenever I load this view, it report an infinite loop in the console, and I don't know why.
When I print just text it works fine, but whenever I try to call a function on each loop, it reports an infinite loop.
I don't know why this happens and any possible recommendation or suggestion will be greatly appreciated. Thank you.
Your problem is this:
v-bind:id="mapID = report.started.toUpperCase()"
You are changing a reactive data property during rendering, which triggers a re-render, which makes you change the property again, which triggers a re-render, and so on ...
So in short: Don't do this. I'm not sure what you are trying to achieve, so I'm not sure what to advise instead. Maybe just this?
v-bind:id="report.started.toUpperCase()"
I have a field that is stateful, and I also have it hooked up to the change event... when its value changes, I want to perform some operation. However, because it's a stateful field, the change event fires when I go back to this view, and unfortunately, the change event fires before the ViewController's init method, which means I will not be able to access my reference lookup.
In the following example, run it, change the date, and then re-run the application... you'll see a console.log that appears for the change, and then for the init. I realize I could set up the handler in the init method, but that just seems silly. I also realize I could create myField as a private var and access it that way, but that also seems silly. And yes, I could change to the select event, but that's not what I want to do. Anyone have any thoughts? Here's an example:
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.myView',
init: function() {
console.log('init fired', this.lookupReference('myField'))
},
onChange: function(value) {
console.log('onChange fired', this.lookupReference('myField'));
}
});
Ext.define('MyView', {
extend: 'Ext.container.Container',
controller: 'myView',
renderTo: Ext.getBody(),
items: [{
xtype: 'datefield',
value: new Date(),
stateful: true,
stateId: 'blahblah',
listeners:{
change: 'onChange'
}
}, {
xtype: 'datefield',
value: new Date(),
reference: 'myField'
}]
});
Ext.create('MyView');
}
});
This is because the state mixin is initialized before the controller, this is code taken directly from Ext.Component's constructor:
me.mixins.state.constructor.call(me);
me.addStateEvents('resize');
controller = me.getController();
if (controller) {
controller.init(me);
}
There is no config to change this behavior. Honestly, I've never seen someone make a form field's value stateful.
You can use the buffer config to delay event firing.
This has an advantage of setting up the event after the controller is initialised.
The solution:
listeners: {
change: {
buffer: 300,
fn: 'onChange'
}
}
An Alternative is to handle 'beforestaterestore` event of the stateful field and apply the state value only after controller is initialised.
listeners: {
beforestaterestore: function (field, state){
var controller = field.up().getController();
Ext.Function.interceptAfter(controller, 'init', function(){
field.setValue(state.value); // update
},this);
return false;
}
}
According to ko's component documentation on a component lifecycle:
If the component binding’s name value changes observably, or if an
enclosing control-flow binding causes the container element to be
removed, then any dispose function on the viewmodel is called just
before the container element is removed from the DOM
I am not sure why my component is being disposed on this fiddle.
<div data-bind='component: { name: "some-component", params: foo }'>
<p data-bind="text: name"></p>
</div>
function ComponentViewModel(params) {
}
ComponentViewModel.prototype.dispose = function() {
console.log('disposing...');
};
ko.components.register('some-component', {
viewModel: ComponentViewModel,
template : '<div></div>'
});
var rootvm = {
foo : ko.observable('1')
};
ko.applyBindings(rootvm);
setTimeout(function() {
rootvm.foo('2'); // this is disposing ComponentViewModel, why ??
}, 3000);
I can't see any of above's points in the documentation occurring on my fiddle. I certainly don't expect a component to be disposed and re-instantiated if the params injected change.
Any ideas why this is happening?
You are passing component parameters in the wrong way: KnockoutJs requires an object with keys and values, you are passing in an observable. I didn't dig into the details of why that ends up triggering disposal, but if you pass an object as it expects, the dispose function is not invoked anymore.
<div data-bind='component: { name: "some-component", params: {foo: foo} }'>
<p data-bind="text: name"></p>
</div>
I'm trying to get a custom extjs component to render either a green-check or red-x image, based on a true/false value being bound to it.
There's a couple of other controls that previous developers have written for rendering custom labels/custom buttons that I'm trying to base my control off but I'm not having much luck.
I'd like to be able to use it in a view as follows where "recordIsValid" is the name of the property in my model. (If I remove the xtype: it just renders as true/false)
{
"xtype": "booldisplayfield",
"name": "recordIsValid"
}
Here's what I have so far, but ExtJS is pretty foreign to me.
Ext.define('MyApp.view.ux.form.BoolDisplayField', {
extend: 'Ext.Component',
alias : 'widget.booldisplayfield',
renderTpl : '<img src="{value}" />',
autoEl: 'img',
config: {
value: ''
},
initComponent: function () {
var me = this;
me.callParent(arguments);
this.renderData = {
value: this.getValue()
};
},
getValue: function () {
return this.value;
},
setValue: function (v) {
if(v){
this.value = "/Images/booltrue.png";
}else{
this.value = "/Images/boolfalse.png";
}
return this;
}
});
I'd taken most of the above from a previous custom linkbutton implementation. I was assuming that setValue would be called when the model-value for recordIsValid is bound to the control. Then based on whether that was true or false, it would override setting the value property of the control with the correct image.
And then in the initComponent, it would set the renderData value by calling getValue and that this would be injected into the renderTpl string.
Any help would be greatly appreciated.
You should use the tpl option instead of the renderTpl one. The later is intended for rendering the component structure, rather that its content. This way, you'll be able to use the update method to update the component.
You also need to call initConfig in your component's constructor for the initial state to be applied.
Finally, I advice to use applyValue instead of setValue for semantical reasons, and to keep the boolean value for getValue/setValue.
Ext.define('MyApp.view.ux.form.BoolDisplayField', {
extend: 'Ext.Component',
alias : 'widget.booldisplayfield',
tpl: '<img src="{src}" />',
config: {
// I think you should keep the true value in there
// (in order for setValue/getValue to yield the expected
// result)
value: false
},
constructor: function(config) {
// will trigger applyValue
this.initConfig(config);
this.callParent(arguments);
},
// You can do this in setValue, but since you're using
// a config option (for value), it is semantically more
// appropriate to use applyValue. setValue & getValue
// will be generated anyway.
applyValue: function(v) {
if (v) {
this.update({
src: "/Images/booltrue.png"
});
}else{
this.update({
src: "/Images/boolfalse.png"
});
}
return v;
}
});
With that, you can set your value either at creation time, or later, using setValue.
// Initial value
var c = Ext.create('MyApp.view.ux.form.BoolDisplayField', {
renderTo: Ext.getBody()
,value: false
});
// ... that you can change later
c.setValue(true);
However, you won't be able to drop this component as it is in an Ext form and have it acting as a full fledged field. That is, its value won't be set, retrieved, etc. For that, you'll have to use the Ext.form.field.Field mixin. See this other question for an extended discussion on the subject.