i try to implement a anonymous login with Ember and Firebase. I have successfully configure my project for use with Firebase and Emberfire, and i can login to my Firebase. But when i try to save user information in a Session initializer, i can't retrieve it to make my controllers aware of the user state.
This is my code :
I have a sidebar in my application.hbs that i want to display if the user is connected.
<div class="container-fluid" id="main">
<div class="row">
{{#if loggedIn}}
<aside class="col-xs-3">
{{outlet sidebar}}
</aside>
<div class="col-xs-9">
{{outlet}}
</div>
{{else}}
<div class="col-xs-12">
{{outlet}}
</div>
{{/if}}
</div>
</div>
Inside of my application.js controller i try to define a computed property :
import Ember from "ember";
var ApplicationController = Ember.ObjectController.extend({
loggedIn :function() {
console.log("Tota", this.session.get('isConnected'));
return this.session.get('isConnected');
}.property(this.session.get('isConnected')),
});
export default ApplicationController;
this.session references an Initializer that is inject inside of controllers and routes :
import Ember from 'ember';
export function initialize(container, application) {
var session = Ember.Object.extend({
authData : [],
user : null,
login : function(authData, user) {
console.log(authData);
console.log(user);
this.set('authData', authData);
this.set('user',user);
},
getUser: function() {
return this.get('user');
},
getAuthData: function() {
return this.get('authData');
},
isConnected : function() {
return (this.get('user') == null) ? false : true;
}.property('user')
});
application.register('session:main', session, { singleton: true });
// Add `session` object to route to check user
application.inject('route', 'session', 'session:main');
// Add `session` object to controller to visualize in templates
application.inject('controller', 'session', 'session:main');
}
export default {
name: 'session',
initialize: initialize
};
And this is my LoginController :
import Ember from "ember";
var LoginController = Ember.ObjectController.extend({
model : {},
ages : function() {
var ages = Ember.A();
for(var i = 18; i <= 99; i++) {
ages.push(i);
}
return ages;
}.property(),
sexs : ['Male', 'Female'],
actions : {
logIn : function() {
var data = this.getProperties("name", "age", "sex");
var that = this;
this.database.authAnonymously(function(error, authData) {
if (error) {
console.log(error);
} else {
var newUser = that.store.createRecord('user', {
name: data['name'],
age: data['age'],
sex:(data['age'] === "Male" ? 0 : 1)
});
newUser.save();
that.session.login(authData, newUser);
console.log("Toto", that.session.get('isConnected'));
that.transitionToRoute('chat');
}
});
}
}
});
export default LoginController;
So in my application.js, if i define loggedIn to be just a property() not property(this.session.get('isConnected'). loggedIn is not refreshed when the user connects to the application. If i tell it to computes with " this.session.get('isConnected') ", Ember tells me that "this.session" is not defined.
How to refresh this value, to tell to my template to display sidebar if my user is connected?
Simple answer
var ApplicationController = Ember.ObjectController.extend({
loggedIn :Em.computed.alias('session.isConnected'),
// or
loggedIn : function(){
return this.get('session.isConnected');
}.property('session.isConnected')
});
Your problem was your dependencies. Either you weren't watching it (property()) or you were crashing because this.session doesn't exist in the scope of the window. And I doubt Ember was really yelling it at you, more of just the javascript engine while it parsing your javascript.
loggedIn :function() {
console.log("Tota", this.session.get('isConnected'));
return this.session.get('isConnected');
}.property(),
// this is resolved while defining the controller, think of its scope
It is resolved like this:
var tmp = this.session.get('isConnected');
var tmp2 = function() {
console.log("Tota", this.session.get('isConnected'));
return this.session.get('isConnected');
}.property(tmp);
var tmp3 = {
loggedIn: tmp2
};
var ApplicationController = Ember.ObjectController.extend(tmp3);
Related
I have a main page containing a component called single-contact as below:
<el-row id="rowContact">
<!-- Contacts -->
<el-scrollbar wrap-class="list" :native="false">
<single-contact ref="singleContact"></single-contact>
</el-scrollbar>
</el-row>
And I want to dynamically render this component after AJAX polling, so in SingleContact.vue I use $axios and mounted() to request the data from the backend. And I want to render the component using v-for. I have my code as:
<template>
<div :key="componentKey">
<el-row id="card" v-for="contact in contacts" :key="contact.convUsername">
<div id="avatar" ><el-avatar icon="el-icon-user-solid"></el-avatar></div>
<h5 id='name' v-if="contact">{{contact.convUsername}}</h5>
<div id='btnDel'><el-button size="medium" type="danger" icon="el-icon-delete" v-on:click="delContact(contact.convUsername)"></el-button></div>
</el-row>
</div>
</template>
And the data structure is:
data() {
return {
timer: null,
contacts: []
}
And the method of Ajax polling is:
loadContacts () {
var _this = this
console.log('loading contacts')
console.log(localStorage.getItem('username'))
this.$axios.post('/msglist',{
ownerUsername: localStorage.getItem('username')
}).then(resp => {
console.log('success')
var json = JSON.stringify(resp.data);
_this.contacts = JSON.parse(json);
console.log(json);
console.log(_this.contacts[0].convUserName);
// }
}).catch(failResponse => {
console.log(failResponse)
})
}
This is what I get in the console:
Console Result
And the mounted method I compute is as:
beforeMount() {
var self = this
this.$axios.post('/msglist',{
ownerUsername: localStorage.getItem('username')
}).then(resp => {
this.$nextTick(() => {
self.contacts = resp.data
})
}).catch(failResponse => {
console.log(failResponse)
})
},
mounted() {
this.timer = setInterval(this.loadContacts(), 1000)
this.$nextTick(function () {
this.loadContacts()
})
},
beforeDestroy() {
clearInterval(this.timer)
this.timer = null
}
I can get the correct data in the console. It seems that the backend can correctly send json to the front, and the front can also receive the right result. But the page just doesn't render as expected.
Any advice would be great! Thank you in advance!
I am creating an SPA with Laravel and VUEJS. Everything works well:
- The routing changes accurately from /people to /people?page=2
- The data in the table displays correctly.
But, the problem is, if I click the pagination, the data inside the table doesn't update. The URL is updated /people?page=3 but records are still the same. Here are my codes below.
routes.js
export default[
{
path:'/people?page=:page',
component: ListPeople,
name: 'people.paginate',
meta:{
title: 'Paginate'
}
},
{
path: '/people',
component: ListPeople,
name: 'people.list',
meta: {
title: 'People List',
}
},
ListPeople.vue
I have listed here the table.
<template>
<div class="container">
<h1>People List</h1>
<vue-table v-bind="{data:people.data,columns}"></vue-table>
<vue-pagination :pagination="people"
#paginate="getPeople"
:offset="4">
</vue-pagination>
</div>
Paginate.vue
From my pagination, I have something like below:
<template>
<!-- more codes here -->
<router-link class="page-link" :to="{ name:'people.paginate', params:{ page: page }}">{{ page }}</router-link>
<!-- more codes here -->
</template>
<script>
export default{
props: {
pagination: {
type: Object,
required: true
},
offset: {
type: Number,
default: 4
}
},
computed: {
pagesNumber() {
if (!this.pagination.to) {
return [];
}
let from = this.pagination.current_page - this.offset;
if (from < 1) {
from = 1;
}
let to = from + (this.offset * 2);
if (to >= this.pagination.last_page) {
to = this.pagination.last_page;
}
let pagesArray = [];
for (let page = from; page <= to; page++) {
pagesArray.push(page);
}
return pagesArray;
}
},
methods : {
changePage(page) {
this.pagination.current_page = page;
this.$emit('paginate',page);
}
}
}
</script>
You need to add a key to your vue-table component that changes when the pagination changes.
<vue-table v-bind="{data:people.data,columns}" :key="currentPage" ></vue-table>
So in your ListPeople component perhaps in your getPeople method update the key as you paginate.
data(){
return:{
currentPage: 1;
}
}
methods:{
getPeople(page){
//.. do something
this.currentPage = page;
}
}
If you include a key on a child component, when the parent template renders, Vue sets a getter/setter on the key and automatically adds it to the watcher instance of the component. So when the key changes it automatically rerenders the child component. Without the key the child component will remain in its cached state.
i have 2 data from API
1. Category Food
2. Finish Good
how can i show 2 data from API in 1 page vue,
I only can show 1 data from API
this is what i tried
export default {
data(){
items:[],
finish_goods:[],
created() {
let uri = 'http://192.168.10.75:8000/api/finish_goods'; // Data 1
this.axios.get(uri).then(response => {
this.items = response.data.data;
});
},
created() {
let uri = 'http://192.168.10.75:8000/api/cat_foods'; // Data 2
this.axios.get(uri).then(response => {
this.finish_goods = response.data.data;
});
}
},
methods: {}
}
You're along the right lines, but it looks like your template syntax is a bit messed up...
// Make sure axios is installed via npm, you can skip this step
// if you've declared window.axios = axios somewhere in your app...
import axios from 'axios';
export default {
// Data must be a function that returns the initial state object...
data() {
return {
finishGoods: [],
catFoods: []
};
},
// Created is a hook and can only be defined once, think of it like an event listener...
created() {
let finishGoodsUri = 'http://192.168.10.75:8000/api/finish_goods';
// Fetch finish goods, note that I'm not calling this.axios...
axios.get(finishGoodsUri).then(response => {
this.finishGoods = response.data.data;
});
let catFoodsUri = 'http://192.168.10.75:8000/api/cat_foods';
// Fetch cat foods...
axios.get(catFoodsUri).then(response => {
this.catFoods = response.data.data;
});
}
}
Now in your template you can do the following:
<template>
<div>
<div v-for="finishGood in finishGoods">
{{ finishGood.attribute }}
</div>
<div v-for="catFood in catFoods">
{{ catFood.attribute }}
</div>
</div>
</template>
my advice, combine the API as 1
created() {
let uri = 'http://192.168.10.75:8000/api/combine_data'; // sample
this.axios.get(uri).then(response => {
this.finish_goods = response.data.data.goods;
this.items = response.data.data.foods;
});
}
On the official pages and in the GitHub issues for redux-form there are more than one example of how to work with initialValues however I cannot find a single one that focuses on explaining how initialValues can be set in response to an asynchronous source.
The main case that I have in mind is something like a simple CRUD application where a user is going to edit some entity that already exists. When the view is first opened and the redux-form component is mounted but before the component is rendered the initialValues must be set. Lets say that in this example that the data is loaded on demand when the component is first mounted and rendered for the first time. The examples show setting initialValues based on hard coded values or the redux store state but none that I can find focus on how to set the initialValues based on something async like a call to XHR or fetch.
I'm sure I'm just missing something fundamental so please point me in the right direction.
References:
Initializing Form State
Handling form defaults
What is the correct way to populate a dynamic form with initial data?
EDIT: Updated Solution from ReduxForm docs
This is now documented in the latest version of ReduxForm, and is much simpler than my previous answer.
The key is to connect your form component after decorating it with ReduxForm. Then you will be able to access the initialValues prop just like any other prop on your component.
// Decorate with reduxForm(). It will read the initialValues prop provided by connect()
InitializeFromStateForm = reduxForm({
form: 'initializeFromState'
})(InitializeFromStateForm)
// now set initialValues using data from your store state
InitializeFromStateForm = connect(
state => ({
initialValues: state.account.data
})
)(InitializeFromStateForm)
I accomplished this by using the redux-form reducer plugin method.
The following demos fetching async data and pre-populating a user form with response.
const RECEIVE_USER = 'RECEIVE_USER';
// once you've received data from api dispatch action
const receiveUser = (user) => {
return {
type: RECEIVE_USER,
payload: { user }
}
}
// here is your async request to retrieve user data
const fetchUser = (id) => dispatch => {
return fetch('http://getuser.api')
.then(response => response.json())
.then(json => receiveUser(json));
}
Then in your root reducer where you include your redux-form reducer you would include your reducer plugin that overrides the forms values with the returned fetched data.
const formPluginReducer = {
form: formReducer.plugin({
// this would be the name of the form you're trying to populate
user: (state, action) => {
switch (action.type) {
case RECEIVE_USER:
return {
...state,
values: {
...state.values,
...action.payload.user
}
}
default:
return state;
}
}
})
};
const rootReducer = combineReducers({
...formPluginReducer,
...yourOtherReducers
});
Finally you include you combine your new formReducer with the other reducers in your app.
Note The following assumes that the fetched user object's keys match the names of the fields in the user form. If this is not the case you will need to perform an additional step on the data to map fields.
By default, you may only initialize a form component once via initialValues. There are two methods to reinitialize the form component with new "pristine" values:
Pass a enableReinitialize prop or reduxForm() config parameter set to true to allow the form the reinitialize with new "pristine" values every time the initialValues prop changes. To keep dirty form values when it reinitializes, you can set keepDirtyOnReinitialize to true. By default, reinitializing the form replaces all dirty values with "pristine" values.
Dispatch the INITIALIZE action (using the action creator provided by redux-form).
Referenced from : http://redux-form.com/6.1.1/examples/initializeFromState/
Could you fire the dispatch on componentWillMount(), and set the state to loading.
While it is loading, render a spinner for example and only when the request returns with the values, update the state, and then re-render the form with the values??
Here is minimal working example on how to set initialValues based on async source.
It uses initialize action creator.
All values from initialValues shouldn't be undefined, or you will get an infinite loop.
// import { Field, reduxForm, change, initialize } from 'redux-form';
async someAsyncMethod() {
// fetch data from server
await this.props.getProducts(),
// this allows to get current values of props after promises and benefits code readability
const { products } = this.props;
const initialValues = { productsField: products };
// set values as pristine to be able to detect changes
this.props.dispatch(initialize(
'myForm',
initialValues,
));
}
While this method may not be the best solution, it works well enough for my needs:
AJAX request to API on entry
Initializes form with data when request has been fulfilled or displays a server error
Resetting form will still reset to initial seed data
Allows the form to be reused for other purposes (for example, a simple if statement could bypass setting initial values): Add Post and Edit Post or Add Comment and Edit Comment...etc.
Data is removed from Redux form on exit (no reason to store new data in Redux since it's being re-rendered by a Blog component)
Form.jsx:
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { browserHistory, Link } from 'react-router';
import { editPost, fetchPost } from '../../actions/BlogActions.jsx';
import NotFound from '../../components/presentational/notfound/NotFound.jsx';
import RenderAlert from '../../components/presentational/app/RenderAlert.jsx';
import Spinner from '../../components/presentational/loaders/Spinner.jsx';
// form validation checks
const validate = (values) => {
const errors = {}
if (!values.title) {
errors.title = 'Required';
}
if (!values.image) {
errors.image = 'Required';
}
if (!values.description) {
errors.description = 'Required';
} else if (values.description.length > 10000) {
errors.description = 'Error! Must be 10,000 characters or less!';
}
return errors;
}
// renders input fields
const renderInputField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} className="form-details complete-expand" placeholder={label} type={type}/>
{touched && error && <div className="error-handlers "><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
</div>
</div>
)
// renders a text area field
const renderAreaField = ({ textarea, input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<textarea {...input} className="form-details complete-expand" placeholder={label} type={type}/>
{touched && error && <div className="error-handlers"><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
</div>
</div>
)
class BlogPostForm extends Component {
constructor() {
super();
this.state = {
isLoaded: false,
requestTimeout: false,
};
}
componentDidMount() {
if (this.props.location.query.postId) {
// sets a 5 second server timeout
this.timeout = setInterval(this.timer.bind(this), 5000);
// AJAX request to API
fetchPost(this.props.location.query.postId).then((res) => {
// if data returned, seed Redux form
if (res.foundPost) this.initializeForm(res.foundPost);
// if data present, set isLoaded to true, otherwise set a server error
this.setState({
isLoaded: (res.foundPost) ? true : false,
serverError: (res.err) ? res.err : ''
});
});
}
}
componentWillUnmount() {
this.clearTimeout();
}
timer() {
this.setState({ requestTimeout: true });
this.clearTimeout();
}
clearTimeout() {
clearInterval(this.timeout);
}
// initialize Redux form from API supplied data
initializeForm(foundPost) {
const initData = {
id: foundPost._id,
title: foundPost.title,
image: foundPost.image,
imgtitle: foundPost.imgtitle,
description: foundPost.description
}
this.props.initialize(initData);
}
// onSubmit => take Redux form props and send back to server
handleFormSubmit(formProps) {
editPost(formProps).then((res) => {
if (res.err) {
this.setState({
serverError: res.err
});
} else {
browserHistory.push(/blog);
}
});
}
renderServerError() {
const { serverError } = this.state;
// if form submission returns a server error, display the error
if (serverError) return <RenderAlert errorMessage={serverError} />
}
render() {
const { handleSubmit, pristine, reset, submitting, fields: { title, image, imgtitle, description } } = this.props;
const { isLoaded, requestTimeout, serverError } = this.state;
// if data hasn't returned from AJAX request, then render a spinner
if (this.props.location.query.postId && !isLoaded) {
// if AJAX request returns an error or request has timed out, show NotFound component
if (serverError || requestTimeout) return <NotFound />
return <Spinner />
}
// if above conditions are met, clear the timeout, otherwise it'll cause the component to re-render on timer's setState function
this.clearTimeout();
return (
<div className="col-sm-12">
<div className="form-container">
<h1>Edit Form</h1>
<hr />
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<Field name="title" type="text" component={renderInputField} label="Post Title" />
<Field name="image" type="text" component={renderInputField} label="Image URL" />
<Field name="imgtitle" component={renderInputField} label="Image Description" />
<Field name="description" component={renderAreaField} label="Description" />
<div>
<button type="submit" className="btn btn-primary partial-expand rounded" disabled={submitting}>Submit</button>
<button type="button" className="btn btn-danger partial-expand rounded f-r" disabled={ pristine || submitting } onClick={ reset }>Clear Values</button>
</div>
</form>
{ this.renderServerError() }
</div>
</div>
)
}
}
BlogPostForm = reduxForm({
form: 'BlogPostForm',
validate,
fields: ['name', 'image', 'imgtitle', 'description']
})(BlogPostForm);
export default BlogPostForm = connect(BlogPostForm);
BlogActions.jsx:
import * as app from 'axios';
const ROOT_URL = 'http://localhost:3001';
// submits Redux form data to server
export const editPost = ({ id, title, image, imgtitle, description, navTitle }) => {
return app.put(`${ROOT_URL}/post/edit/${id}?userId=${config.user}`, { id, title, image, imgtitle, description, navTitle }, config)
.then(response => {
return { success: response.data.message }
})
.catch(({ response }) => {
if(response.data.deniedAccess) {
return { err: response.data.deniedAccess }
} else {
return { err: response.data.err }
}
});
}
// fetches a single post from the server for front-end editing
export const fetchPost = (id) => {
return app.get(`${ROOT_URL}/posts/${id}`)
.then(response => {
return { foundPost: response.data.post}
})
.catch(({ response }) => {
return { err: response.data.err };
});
}
RenderAlert.jsx:
import React, { Component } from 'react';
const RenderAlert = (props) => {
const displayMessage = () => {
const { errorMessage } = props;
if (errorMessage) {
return (
<div className="callout-alert">
<p>
<i className="fa fa-exclamation-triangle" aria-hidden="true"/>
<strong>Error! </strong> { errorMessage }
</p>
</div>
);
}
}
return (
<div>
{ displayMessage() }
</div>
);
}
export default RenderAlert;
Reducers.jsx
import { routerReducer as routing } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
form: formReducer,
routing
});
export default rootReducer;
use this :
UpdateUserForm = reduxForm({
enableReinitialize: true,
destroyOnUnmount: false,
form: 'update_user_form' // a unique identifier for this form
})(UpdateUserForm);
UpdateUserForm = connect(
(state) => ({
initialValues: state.userManagment.userSingle
})
)(UpdateUserForm);
export default UpdateUserForm;
I'm newbie of angularjs developing and i wrote this simple app, but don't understand how i can update view, after the model il loaded from ajax request on startup!
This code don't work when I add delay into photos.php, using:
sleep(3);
for simulate remote server delay! instead if search.php is speedy it work!!
<!doctype html>
<html ng-app="photoApp">
<head>
<title>Photo Gallery</title>
</head>
<body>
<div ng-view></div>
<script src="../angular.min.js"></script>
<script>
'use strict';
var photos = []; //model
var photoAppModule = angular.module('photoApp', []);
photoAppModule.config(function($routeProvider) {
$routeProvider.when('/photos', {
templateUrl: 'photo-list.html',
controller: 'listCtrl' });
$routeProvider.otherwise({redirectTo: '/photos'});
})
.run(function($http) {
$http.get('photos.php')//load model with delay
.success(function(json) {
photos = json; ///THE PROBLEM HERE!! if photos.php is slow DON'T update the view!
});
})
.controller('listCtrl', function($scope) {
$scope.photos = photos;
});
</script>
</body>
</html>
output of photos.php
[{"file": "cat.jpg", "description": "my cat in my house"},
{"file": "house.jpg", "description": "my house"},
{"file": "sky.jpg", "description": "sky over my house"}]
photo-list.html
<ul>
<li ng-repeat="photo in photos ">
<a href="#/photos/{{ $index }}">
<img ng-src="images/thumb/{{photo.file}}" alt="{{photo.description}}" />
</a>
</li>
</ul>
EDIT 1, Defer solution:
.run(function($http, $q) {
var deferred = $q.defer();
$http.get('photos.php')//load model with delay
.success(function(json) {
console.log(json);
photos = json; ///THE PROBLEM!! if photos.php is slow DON'T update the view!
deferred.resolve(json);//THE SOLUTION!
});
photos = deferred.promise;
})
EDIT 2, Service solution:
...
//require angular-resource.min.js
angular.module('photoApp.service', ['ngResource']).factory('photoList', function($resource) {
var Res = $resource('photos.php', {},
{
query: {method:'GET', params:{}, isArray:true}
});
return Res;
});
var photoAppModule = angular.module('photoApp', ['photoApp.service']);
...
.run(function($http, photoList) {
photos = photoList.query();
})
...
The short answer is this:
.controller('listCtrl', ['$scope', '$timeout', function($scope, $timeout) {
$timeout(function () {
$scope.photos = photos;
}, 0);
}]);
The long answer is: Please don't mix regular javascript and angular like this. Re-write your code so that angular knows what's going on at all times.
var photoAppModule = angular.module('photoApp', []);
photoAppModule.config(function($routeProvider) {
$routeProvider.when('/photos', {
templateUrl: 'photo-list.html',
controller: 'listCtrl'
});
$routeProvider.otherwise({redirectTo: '/photos'});
});
photoAppModule.controller('listCtrl', ['$scope', function($scope) {
$scope.photos = {};
$http.get('photos.php') // load model with delay
.success(function(json) {
$scope.photos = json; // No more problems
});
}]);
use broadcast
//service
var mydata = [];
this.update = function(){
$http.get(url).success(function(data){
mydata = data;
broadcastMe();
});
};
this.broadcastMe = function(){
$rootScope.$broadcast('mybroadcast');
};
//controller
$scope.$on('mybroadcast', function(){
$scope.mydata = service.mydata;
};
http://bresleveloper.blogspot.co.il/
EDIT:couple of days ago i've learned the best practice
http://bresleveloper.blogspot.co.il/2013/08/breslevelopers-angularjs-tutorial.html
I think you're better off using high level angular services for data transfer, also look into promises and services:
http://docs.angularjs.org/api/ng.$q
You need to bind an element in your view to a property (simple or object) of your $scope object. Once the $scope object is updated the view should be updated on its own. That is the beauty of AngularJS.
EDIT:
Please register your controller as
photoAppModule.controller('listCtrl', function($scope){
$scope.photos = photos;
});
If photos variable is not available, then you might have to create a service with the variable and inject in the controller.