I am working on a FAQ page with vue.js
here is the example so far:
<li v-for="i in items | searchFor searchString"
v-on:click="toggleCollapse(i)"
:class="{ collapsed: i.collapse, expanded: !i.collapse }"
>
<p><strong>{{i.q}}</strong></p>
<p>{{i.a}}</p>
</li>
var vm = new Vue({
el: 'body',
data: {
items:[
{q:"test1", a:"a1", collapse:true},
{q:"test2", a:"a2", collapse:true},
{q:"test3", a:"a3", collapse:true},
{q:"test4", a:"a4", collapse:true},
{q:"test5", a:"a5", collapse:true},
{q:"test6", a:"a6", collapse:true}
]
},
methods: {
toggleCollapse: function(i) {
var self = this;
if(i.collapse){
self.items.forEach(function(a) {
a.collapse = true;
});
i.collapse = false;
}else{
i.collapse = true;
}
}
}
});
when user click on one of the items, the item will be expanded. The rest will be collapsed.
I want to call a jquery animation scrollTop function to scroll to the expanded item after users click.
I've tried vue instance such as watch, ready, mounted, updated etc but noe of them seems working.
Any ideas?
I found the solution, nextTick willdo:
methods: {
toggleCollapse: function(i) {
var self = this;
if(i.collapse){
self.items.forEach(function(a) {
a.collapse = true;
});
i.collapse = false;
}else{
i.collapse = true;
}
this.$nextTick(function(){
//command here
})
}
}
Related
I am trying to catch vue.js custom event within one component, but it's not catching. What's the problem?
myEventFunc: function() {
this.myEvent = true;
},
clickedFunc: function() {
this.clicked = true;
this.$emit('myevent');
}
JSFiddle Example: https://jsfiddle.net/ucean0rh/1/
I'm not sure if this is a Vue way of dealing with it, but it works in my JSFiddle. Simply call myEventFunc() from within clickedFunc:
new Vue({
el: "#app",
data: {
myEvent: false,
clicked: false,
},
methods: {
myEventFunc: function() {
this.myEvent = true;
},
clickedFunc: function() {
this.clicked = true;
this.myEventFunc();
this.$emit('myevent');
}
}
})
Below is my code:
var CommonHeader = require('./header/CommonHeader.jsx');
var ListOptions = require('./header/ListOptions.jsx');
var SortableTable = require('../shared/SortableTable.jsx');
var ColumnDefinition = require('../shared/SortableTable/ColumnDefinition.jsx');
var DashboardApiActions = require('../../actions-api/DashboardApiActions');
var DashboardStore = require('../../stores/DashboardStore');
function constructList(data) {
var clickFunction = function(dashboardId, e) {
e.preventDefault();
DashboardApiActions.getDetail(dashboardId);
};
return data.map(function(row) {
return {
name : <a href="#" onClick={clickFunction.bind(this, row.id)}>{row.name}</a>,
createdBy : row.createdBy,
shared: "Share to everyone",
popularity: 20
};
});
}
function getState() {
return {
selectedTab: 'dashboard',
pageMetaData : DashboardStore.getPageMetaData(),
hasNextPage : DashboardStore.hasNextPage()
};
}
var List = React.createClass({
getInitialState: function() {
return getState();
},
handleDashboard: function() {
this.setState({
selectedTab: 'dashboard'
});
},
handleFav: function() {
this.setState({
selectedTab: 'fav'
});
},
handlePopular: function() {
this.setState({
selectedTab: 'popular'
});
},
wait: function(ms) {
alert('hi');
var start = new Date().getTime();
var end = start;
while(end < start + ms) {
end = new Date().getTime();
}
},
getDetails() {
var nextPageListener = this.state.hasNextPage ? this.handleNextPage : null;
if(this.state.selectedTab === 'dashboard') {
this.wait(1000);
var details = DashboardStore.getList();
console.log(details);
return (
<SortableTable data={constructList(details)} nextPageListener={nextPageListener} >
<ColumnDefinition dataField="name">Name</ColumnDefinition>
<ColumnDefinition dataField="createdBy">Owner</ColumnDefinition>
<ColumnDefinition dataField="shared">Shared With</ColumnDefinition>
<ColumnDefinition dataField="popularity">Popularity</ColumnDefinition>
</SortableTable>
);
} else if(this.state.selectedTab === 'fav') {
return(
<div className="col-md-12">
<span>Nothing to show</span>
</div>
);
} else if(this.state.selectedTab === 'popular') {
return(
<div className="col-md-12">
<span>Nothing to show</span>
</div>
);
}
},
_onChange : function() {
this.setState(getState());
},
componentDidMount : function() {
DashboardStore.addChangeListener(this._onChange);
},
componentWillUnmount : function() {
DashboardStore.removeChangeListener(this._onChange);
},
handleNextPage : function () {
var currPage = this.state.pageMetaData.pageNumber ? this.state.pageMetaData.pageNumber : 0;
DashboardApiActions.getDashboards(currPage + 1);
},
render: function(){
return(
<div id="dashboard">
<CommonHeader title={"Dashboard"} options={<ListOptions />}
handlePopular={this.handlePopular}
handleDashboard={this.handleDashboard}
handleFav={this.handleFav}/>
{this.getDetails()}
</div>
);
}
});
module.exports = List;
I have 3 tabs. On click of each I need to show some table data. On load My dashboard is selected. The issue is on load table is empty but if I click on some other tab and then again click on My dashboard tab then data is coming.
After debugging thoroughly I understood the problem is time issue, after 1000ms data is coming here -
var details = DashboardStore.getList();
so I called wait() to wait for 1000ms. Now one surprising thing is happening if I add one alert at wait() method then data is coming once I click on ok of alert box. If I remove the alert then on load data not coming anymore.
I checked API is hitting on load and response also coming.
so whats the issue. Please help me. I am stuck for a long time. :-(
It looks like the issue might be that you are using componentDidMount, there is some delay between this function being called and getInitialState so I suspect that you have a race condition between those 2.
Try using componentWillMount instead of componentDidMount.
Like so:
componentWillMount : function() {
DashboardStore.addChangeListener(this._onChange);
},
componentWillUnmount : function() {
DashboardStore.removeChangeListener(this._onChange);
},
I've got a parent component that has 2 child components;
UPDATE
I've rewritten some statements and code to make it more understandable.
Parent: ReservationFormComponent
Children: ReservationTypePanel & ReservationCalendarPanel
The parent component ReservationFormComponent initially displays the ReservationTypePanel only. The other sibling ReservationCalendarPanel is hidden until an item is selected on ReservationTypePanel.
So the problem is when an item is selected in ReservationTypePanel the ReservationCalendarPanel is rendered with initial values set in the ReservationFormStore store. Particularly
initialize: function(){
this.reservationType = void 8;
this.pickupTime = moment().add('minutes',30);
}
So when the ReservationCalendarPanel is rendered, its child Component DateTimeField which accepts the state pickupTime get re-rendered and fires up the onChange event which calls for another action
return DateTimeField({
pickupTime: pickupTime,
onChange: function(time){
// Here is where the action gets called again
this$.getFlux().actions.setReservationPickupTime(time);
}
});
And greets me with this error Uncaught Error: Cannot dispatch an action while another action is being dispatched
I've tried my best to trim down the codes below. I wasn't using JSX because the original code was in LiveScript so I just took the compiled code to display here instead.
This is the parent component ReservationFormComponent
ReservationFormComponent = React.createClass({
get flux(){ // Instantiating Fluxxor
return new Fluxxor.Flux({ // These are the stores
'reservation-form': new ReservationFormStore,
'reservation-types': new ReservationTypeStore
}, { // These are the actions
setReservationType: function(value){
return this.dispatch('SET_RESERVATION_TYPE', value);
},
setReservationPickupTime: function(value){
return this.dispatch('SET_RESERVATION_PICKUP_TIME', value);
}
});
},
componentWillMount: function(){
this.flux.store('reservation-form').addListener('change', this.onChange);
},
onChange: function(){ // This triggers the re-render to display the ReservationCalendarPanel
this.setState({
pickupTime: this.flux.store('reservation-form').pickupTime
});
},
render: function() {
reservationType = this.state.reservationType;
return form({
className: 'container'
}, ReservationTypePanel({
flux: this.flux
}), reservationType ? ReservationCalendarPanel({
flux: this.flux
}) : null // This conditional to mount or not mount the component
);
}
});
The ReservationTypePanel Component. Here, the rendered component listens to onClick event and dispatches setReservationType action.
ReservationTypePanel = React.createClass({
mixins: [fluxxor.FluxMixin(react)],
onSelectReservationType: function(reservationType){
var this$ = this;
return function(event){
this$.getFlux().actions.setReservationType(reservationType);
};
},
render: function() {
var this$ = this;
return ReservationTypeItem({
onClick: this$.onSelectReservationType(type);
})
}
});
The ReservationCalendarPanel Component. Here is where the DateTimeField is rendered and receives the state from the ReservationFormStore and sets the value which causes another dispatch. This is where the error comes.
ReservationCalendarPanel = React.createClass({
mixins: [fluxxor.FluxMixin(react)],
getInitialState: function() {
return {pickupTime: moment()} // sets the current time
},
componentWillMount: function(){
this.getFlux().store('reservation-form').addListener('change-pickup-time', this.onFlux);
},
componentWillUnmount: function(){
this.getFlux().store('reservation-form').removeListener('change-pickup-time', this.onFlux);
},
render: function() {
var this$ = this;
if (this.state.pickupTime) {
pickupTime = moment(this.state.pickupTime);
}
return DateTimeField({
date: pickupTime,
onChange: function(time){
// Here is where the action gets called again
this$.getFlux().actions.setReservationPickupTime(time);
}
});
});
This is the DateTimeField this is where the
DateTimeField = React.createClass({
getInitialState: function(){
return {
text: ''
};
},
componentWillReceiveProps: function(nextProps){
this.setDate(nextProps.date);
},
componentDidMount: function(){
$(this.getDOMNode()).datepicker()
.on('changeDate', this.onChangeDate)
.on('clearDate', this.onChangeDate);
this.setDate(this.props.date);
},
componentWillUnmount: function(){
return $(this.getDOMNode()).datepicker('remove');
},
getDatepickerDate: function(){
return $(this.getDOMNode()).datepicker('getDate');
},
setDate: function(date){
if (!this.isMounted()) {
return;
}
if (moment(date).isSame(this.getDatepickerDate, 'day')) {
// If there is no change between the date that
// is about to be set then just ignore and
// keep the old one.
return;
}
date = date ? moment(date).toDate() : void 8;
$(this.getDOMNode()).datepicker('setDate', date);
},
onChangeDate: function(event){
if (this.props.onChange) {
this.props.onChange(event.date);
}
},
render: function(){
return this.transferPropsTo(input({
type: 'text',
className: 'form-control'
}));
}
});
If in case here is the store:
ReservationFormStore = fluxxor.createStore({
actions: {
SET_RESERVATION_TYPE: 'setReservationType',
SET_RESERVATION_PICKUP_TIME: 'setPickupTime'
},
initialize: function(){
this.reservationType = void 8;
this.pickupTime = moment().add('minutes',30);
},
setReservationType: function(reservationType){
this.reservationType = reservationType;
this.reservationTypeValidate = true;
this.emit('change-reservation-type', this.reservationType);
this.emit('change');
}
setPickupTime: function(pickupTime){
this.pickupTime = pickupTime;
this.pickupTimeValidate = true;
this.emit('change-pickup-time', this.pickupTime);
this.emit('change');
}
});
Trying to get the hang of Mithril, can't really understand one thing. Can I render components on events?
Let's assume I have one parent component:
var MyApp = {
view: function() {
return m("div", [
m.component(MyApp.header, {}),
m("div", {id: "menu-container"})
])
}
};
m.mount(document.body, megogo.main);
It renders the header component (and a placeholder for the menu (do I even need it?)):
MyApp.header = {
view: function() {
return m("div", {
id: 'app-header'
}, [
m('a', {
href: '#',
id: 'menu-button',
onclick: function(){
// this part is just for reference
m.component(MyApp.menu, {})
}
}, 'Menu')
])
}
}
When the user clicks on the menu link I want to load the menu items from my API and only then render the menu.
MyApp.menu = {
controller: function() {
var categories = m.request({method: "GET", url: "https://api.site.com/?params"});
return {categories: categories};
},
view: function(ctrl) {
return m("div", ctrl.categories().data.items.map(function(item) {
return m("a", {
href: "#",
class: 'link-button',
onkeydown: MyApp.menu.keydown
}, item.title)
}));
},
keydown: function(e){
e.preventDefault();
var code = e.keyCode || e.which;
switch(code){
// ...
}
}
};
This part will obviously not work
onclick: function(){
// this part is just for reference
m.component(MyApp.menu, {})
}
So, the question is what is the correct way render components on event?
Try This:
http://jsbin.com/nilesi/3/edit?js,output
You can even toggle the menu.
And remember that you get a promise wrapped in an m.prop from the call to m.request. You'll need to check that it has returned before the menu button can be clicked.
// I'd stick this in a view-model
var showMenu = m.prop(false)
var MyApp = {
view: function(ctrl) {
return m("div", [
m.component(MyApp.header, {}),
showMenu() ? m.component(MyApp.menu) : ''
])
}
};
MyApp.header = {
view: function() {
return m("div", {
id: 'app-header'
}, [
m('a', {
href: '#',
id: 'menu-button',
onclick: function(){
showMenu(!showMenu())
}
}, 'Menu')
])
}
}
MyApp.menu = {
controller: function() {
//var categories = m.request({method: "GET", url: "https://api.site.com/?params"});
var categories = m.prop([{title: 'good'}, {title: 'bad'}, {title: 'ugly'}])
return {categories: categories};
},
view: function(ctrl) {
return m("div.menu", ctrl.categories().map(function(item) {
return m("a", {
href: "#",
class: 'link-button',
onkeydown: MyApp.menu.keydown
}, item.title)
}));
},
keydown: function(e){
e.preventDefault();
var code = e.keyCode || e.which;
switch(code){
// ...
}
}
};
m.mount(document.body, MyApp);
First of all, you'll want to use the return value of m.component, either by returning it from view, or (more likely what you want) put it as a child of another node; use a prop to track whether it's currently open, and set the prop when you wish to open it.
To answer the actual question: by default Mithril will trigger a redraw itself when events like onclick and onkeydown occur, but to trigger a redraw on your own, you'll want to use either m.redraw or m.startComputation / m.endComputation.
The difference between them is that m.redraw will trigger a redraw as soon as it's called, while m.startComputation and m.endComputation will only trigger a redraw once m.endComputation is called the same amount of times that m.startComputation has been called, so that the view isn't redrawn more than once if multiple functions need to trigger a redraw once they've finished.
Looked around SO but couldn't find anything useful, so..
I have a Backbone.js contacts model with a contact card view. This view has many inputs where you can edit the contacts information.
I have many other forms on the page that are NOT backbone models, so they use a 'save button' to save. I basically want this save button to also trigger Contacts.CardView.saveCard(); (which could possibly be FileApp.cardView.saveCard as well? -- some of my code is below.
Is there any way to do this? I thought I could just use the following, but it seems it won't bind an event to anything outside the view?:
events: {
"change input": "change",
"click #save": "saveCard"
},
$('#save').click(function() {
FileApp.cardView.saveCard;
_SAVE.save();
})
CardView
window.Contacts.CardView = Backbone.View.extend({
events: {
"click #save": "saveCard" // doesnt work because #save is outside the view?
},
saveCard: function(e) {
this.model.set({
name:$('#name').val()
});
if (this.model.isNew()) {
var self = this;
FileApp.contactList.create(this.model, {
success:function () {
FileApp.navigate('contacts/' + self.model.id, false);
}
});
} else {
this.model.save();
}
return false;
}
}
Router:
var FileRouter = Backbone.Router.extend({
contactCard:function (id) {
if (this.contactList) {
this.cardList = new Contacts.CardCollection();
var self = this;
this.cardList.fetch({
data: {
"id":id
},
success: function(collection, response) {
if (self.cardView) self.cardView.close();
self.cardView = new Contacts.CardView({
model: collection.models[0]
});
self.cardView.render();
}
});
} else {
CONTACT_ID = id;
this.list();
}
}
});
var FileApp = new FileRouter();
One option is to create your own Events object for this case:
// Before initializing views, etc.
var formProxy = {};
_.extend(formProxy, Backbone.Events);
// Add the listener in the initialize for the CardView
window.Contacts.CardView = Backbone.View.extend({
initialize : function() {
formProxy.on('save', this.saveCard, this);
},
saveCard: function() {
this.model.set({
name:$('#name').val()
});
if (this.model.isNew()) {
var self = this;
FileApp.contactList.create(this.model, {
success:function () {
FileApp.navigate('contacts/' + self.model.id, false);
}
});
} else {
this.model.save();
}
return false;
}
}
// Save
$('#save').click(function() {
formProxy.trigger('save');
});
See: http://documentcloud.github.com/backbone/#Events