Ionic Master Detail view with Firebase auto-generated child key returning JSON $value null - ionic-framework

I am new to Ionic and Firebase. While fetching data for details view I am getting undefined value for $scope.program.
However, I have fetched the complete programs list in Master view. Clicking the list item from master view returns index (0, 1, 2, etc) of the list in console log but not the child key (which I was expecting)
During my research, I found out this question quite relevant to my problem, by Wes Haq. But while implementing the same, it has no change in the result. Please help.
From the above question by Wes Haq, I couldn't find the state provider details. I may be missing something there. Thanks in advance to all.
controllers.js
.controller('myServicesCtrl', ['$scope', '$state', '$stateParams', '$ionicActionSheet', 'AgencyProgService',
function ($scope, $state, $stateParams, $ionicActionSheet, AgencyProgService) {
//Only items relative to the logged/signed in Agency shall be pushed to the scope.items
$scope.programs = AgencyProgService.getPrograms();
}])
.controller('serviceDetailCtrl', ['$scope', '$stateParams', 'AgencyProgService',
function ($scope, $stateParams, AgencyProgService) {
AgencyProgService.getProgram($stateParams.index).then(function (program) {
$scope.program = program;
});
console.log("serviceDetailCtrl: scope.program is: " + $scope.program);
}])
service.js
.service('AgencyProgService', ['$q', '$firebaseArray', '$firebaseObject', 'AgencyDataService', function ($q, $firebaseArray, $firebaseObject, AgencyDataService) {
var ref = firebase.database().ref().child('programs/'); // this is a valid ref with https://my-demo.firebaseio.com/programs
var agencyIDfromPrograms = AgencyDataService.getAgencyUID();
var refFilter = ref.orderByChild("agencyID").equalTo(agencyIDfromPrograms);
return {
getPrograms: function () {
return $firebaseArray(refFilter);
},
getProgram: function (programId) {
console.log("getProgram: ID is: " + programId + "And refFilter to use is: " + ref);
var deferred = $q.defer();
var programRef = ref.child(programId); // here programId should be the autogenerated child key from fire DB "programs"
var program = $firebaseObject(programRef);
deferred.resolve(program);
return deferred.promise;
}
}
}])
Views:
myServices.html
<ion-list id="myServices-list9">
<ion-item id="myServices-list-item11"
ng-repeat="item in programs" ui- sref="serviceDetail({index: $index})">
<div class="item-thumbnail-left">
<i class="icon"></i>
<h2>{{item.progName}} </h2>
<p>{{item.servType}} for: {{item.servHours}}</p>
<p>From: {{item.servStartDate}}</p>
</div>
</ion-item>
</ion-list>
serviceDetail.html
<div class="list card">
<div class="item item-avatar">
<img src="{{item.user.picture.thumbnail}} " />
<h1>{{program.servContact}}</h1>
<h2>{{program.$id}} {{program.progName}}</h2>
<p>{{item.servContact}} {{item.servStartDate}}</p>
</div>
<pre> {{program | json}} </pre>
From serviceDetail view, no data from program is visible, as you can see from the screen shot below. List card for serviceDetail. on clicking 2nd item on myServices, it shows this detail screen with $id as 1. Expected is the child key for programs:
Firebase data structure screenshot for child programs is as below,
Appreciate your suggestions.

Related

What is the best way to post form with multiple components using Vue js

as I'm on my Vue spree (started recently but so far I'm really enjoying learning this framework) couple of questions rised up. One of which is how to post form from multiple components. So before I continue forward I wanted to ask you what are you thinking about this way of structuring and point me in right direction if I'm wrong.
Here it goes.
I'm working on a SPA project using ASP.NET CORE 2.1 and Vue JS Template (with webpack)(https://github.com/MarkPieszak/aspnetcore-Vue-starter) and my project is structured in several containers, something like this:
In my app-root i registered several containers
<template>
<div id="app" class="container">
<app-first-container></app-first-container>
<app-second-container></app-second-container>
<!--<app-third-container></app-third-container>-->
<app-calculate-container></app-calculate-container>
<app-result-container></app-result-container>
</div>
</template>
<script>
// imported templates
import firstContainer from './first-container'
import secondContainer from './second-container'
import calculateContainer from './calculateButton-container'
//import thirdContainer from './third-container'
import resultContainer from './result-container'
export default {
components: {
'app-first-container': firstContainer,
'app-second-container': secondContainer,
// 'app-third-container': thirdContainer,
'app-calculate-container': calculateContainer,
'app-result-container': resultContainer
}
}
</script>
In my first container I'm having several dropdowns and two input fields with my script file where I'm fetching data from API and filling dropdowns and input fields with fetched data.
Something like this ( entered some dummy code for demonstration)
<template>
<div>
<h1>Crops table</h1>
<p>This component demonstrates fetching data from the server. {{dataMessage}}</p>
<div class="form-row">
<div class="form-group col-md-6">
<label for="exampleFormControlSelect1" class="col-form-label-sm font-weight-bold">1. Some text</label>
<select class="form-control" id="exampleFormControlSelect1" v-model="pickedCropType" #change="getCropsByType()">
<option v-for="(cropType, index) in cropTypes" :key="index" :value="cropType.id" :data-imagesrc="cropType.imgPath">{{ cropType.name }}</option>
</select>
</div>
<div class="form-group col-md-6">
<label for="exampleFormControlSelect2" class="col-form-label-sm font-weight-bold">2. Some text</label>
<select class="form-control" id="exampleFormControlSelect2">
<option v-for="(crop, index) in cropSelectList" :key="index" :value="crop.id">{{ crop.name }}</option>
</select>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
export default {
data() {
return {
cropTypes: null,
cropSelectList: null,
crops: null,
pickedCropType: null,
}
},
methods: {
loadPage: async function () {
try {
//Get crop types and create a new array with crop types with an added imgPath property
var cropTypesFinal = [];
let responseCropTypes = await this.$http.get(`http://localhost:8006/api/someData`);
responseCropTypes.data.data.forEach(function (element) {
cropTypesFinal.push(tmpType);
});
} catch (err) {
window.alert(err)
console.log(err)
}
},
getCropsByType: async function () {
//Get crops by crop type
let responseCrops = await this.$http.get(`http://localhost:8006/api/crop/Type/${this.pickedCropType}`);
var responseCropsData = responseCrops.data.data;
this.cropSelectList = responseCropsData;
}
},
async created() {
this.loadPage()
}
}
</script>
And in my second container I have different dropdowns and different input fields with different scripts etc.
So, my questions are:
1.) I'm having required data form field in first container and in second container I'm having additional data and my submit button is separated in third container (app-result-container). So, is this proper and logical way of structuring containers if not can you point me in right direction?
2.) Is it smart to input script tag in every container where I'm processing/fetching/submitting some data for that particular container? Should I put scripts tag in separated file and keep structure clean, separating html from js file.
Example:
import { something } from 'something'
export default {
data () {
return {
someData: 'Hello'
}
},
methods: {
consoleLogData: function (event) {
Console.log(this.someData)
}
}
}
3.) Can I send input values from one container to another (In my particular case from first and second container to app-calculate-container(third container))?
How to on submit return results container with calculated imported values
If you want components to communicate or share data with one another, you will need to either emit an event from one component up to the parent and pass it down via props, or use some kind of state management model, like Vuex, where each of your components can listen to the store.
Take a look at this code sandbox: https://codesandbox.io/s/8144oy7xy2
App.vue
<template>
<div id="app">
<child-input #input="updateName" />
<child-output :value="name" />
</div>
</template>
<script>
import ChildInput from "#/components/ChildInput.vue";
import ChildOutput from "#/components/ChildOutput.vue";
export default {
name: "App",
components: {
ChildInput,
ChildOutput
},
data() {
return {
name: ""
};
},
methods: {
updateName(e) {
this.name = e.target.value;
}
}
};
</script>
ChildInput.vue
<template>
<input type="text" #input="changeHandler">
</template>
<script>
export default {
name: "ChildInput",
methods: {
changeHandler(e) {
this.$emit("input", e);
}
}
};
</script>
ChildOutput.vue
<template>
<p>{{ value }}</p>
</template>
<script>
export default {
name: "ChildOutput",
props: {
value: {
type: String,
default: ""
}
}
};
</script>
What's going on?
The ChildInput component is a text field and on every change inside it, fires an event (emits using this.$emit() and passes the whole event up).
When this fires, App is listening to the change, which fires a method that updates the name data property.
Because name is a reactive data property and is being passed down as a prop to the ChildOutput component, the screen re-renders and is updated with the text written.
Neither ChildInput nor ChildOutput knows about one another. It's the parent that listens to the event passed to it, then passes the new prop down.
This way of working is fine and simple to understand, but I would strongly recommend looking at Vuex, as this method can get messy and complicated when you go beyond trivial tasks.

Ionic 2/3 Pushing items in Array based on ID

I'm looking to push items from one page to another, based on the category ID. Then I went to push all the contents from the array(who have the category ID that matches) to the next page.
I'm caught between using an if statement to pass the parameters to the second page, or to call the array in the second page and use an NgIf. Below is my code for a better visual. I'm fairly new to Ionic. Thank you for the help!
First Page
if(id = 1) {
category_id: this.referenceList.category_id = 1;
this.navCtrl.push(ReferencePage,{category_id:"category_id"});
}
else {
console.log("nope")
}
Second Page
<div *ngfor = let reference of referenceList></div>
<div *ngif = category.id === 1> {{reference.referenceField1}} {{reference.referenceField2}} {{reference.media}} </div>
Not sure which one to use and why. Thanks again! Happy to clarify if there's any confusion.
You stored the category_id in a variable, so use this:
this.navCtrl.push(ReferencePage,{category_id:category_id});
In the second page controller you need:
import ( NavParams ) from 'ionic-angular';
category_id: number;
constructor(public navParams: NavParams) {
...
}
ionViewDidLoad() {
this.category_id = this.navParams.get('category_id');
}
Also, your html should be (if I understand what you are doing):
<div *ngFor = let reference of referenceList>
<div *ngIf = category_id === 1>
{{reference.referenceField1}};
{{reference.referenceField2}};
{{reference.media}};
</div>
</div>

In Reactjs how do I update the correct value in the parent state with a callback that's been passed to a nested child component?

I've been on this one for days, and all my reading hasn't helped me find a clean solution for this particular case.
Issue
I can send a parent state value and callback down to a nested component, but once the callback is triggered in the child I don't know how I can send the updated value back to the parent so it can update the correct value.
For instance
Parent Component (Has values and the callback)
Child Component (Values and callback is passed here)
Grand Child Component (Values Updated here and callback triggered)
What is SEEMS to cause the Issue
It seems the issue is I need the original key name in order for "setState" to update the correct value in the parent component(or at least it seems that way), but the child component only has original value and new updated value and has no access to the key associated with original value in the parent component.
Important Notes on Best Practice Surrounding this question
-From what I understand it is bad practice to use refs to handle nested situations like this.
-It seems like there is a cleaner solution than sending a prop for the key and another for the value.
-I'm assuming also that flux might provide a solution to this issue but I feel that there is a basic component to component communication technique or principle that I'm missing here.
Here is a bare bones example of what I'm dealing with.
/*All the values need to be updated here so that the inputs can used for calculation and then sent to a component that displays the output*/
var Calculator =
React.createClass({
getInitialState:function(){
return {
value1: "Enter value 1", /*These values are passed to a nested child component, can't figure how to update the right one*/
value2: "Enter value 2",
}
},
update: function(update){
this.setState(
update
);
},
render: function () {
return (
<div>
<h2>Input</h2>
<Input onClick={this.handleClick} update={this.update} value1={this.state.value1} value2={this.state.value2} /> //pass the values here
<h2>Output</h2>
<Output />
</div>
);
},
handleClick: function () {
//want to update the state for the correct value here
}
});
/* A compenent that is a middle layer between the parent and nested child component I'm working with*/
var Input =
React.createClass({
update: function(){
this.props.update();
},
render:function(){
return (
<div>
<p><InputComponent update={this.update} value={this.props.value1} /> / <InputComponent value={this.props.value2}/></p>//passing down values again
<p><ButtonComponent onClick={this.props.onClick} /></p>
</div>
)
}
});
/*This is the child component that gets the value and call back from the top level component. It will get updates to the values and send them back to change state of the parent component.*/
var InputComponent =
React.createClass({
handleChange: function(event) {
this.props.update();
},
render: function() {
return <input type="text" value={this.props.value} onChange={this.handleChange} />; //this props value has no key associated with it. Cant't make update object ie {originalkey:newValue}
}
});
/* This component is triggered to carry out calculations in the parent class.*/
var ButtonComponent =
React.createClass({
render:function(){
return <button onClick={this.handleClick}> {this.props.txt} </button>
},
handleClick: function(){
this.props.onClick();
}
});
/*The inputs will be calculated and turned to outputs that will displayed here.This component doesn't matter for the question so I left it empty*/
var Output =
React.createClass({
});
Here's an example I just put together on jsfiddle.
Instead of putting update in setState, we pass a value to update from the child component and let the parent set its state.
In the parent, we have:
_update: function(val){
this.setState({
msg: val
});
},
render: function() {
return (
<div>
<p>Message: {this.state.msg}</p>
<Child _update={this._update} />
</div>
);
}
And in the child, we have a _handleClick function that calls the parent _update function with values:
_handleClick: function(){
this.props._update(React.findDOMNode(this.refs.text).value);
},
render: function(){
return (
<div>
<input type="text" ref="text" />
<button onClick={this._handleClick}>Update</button>
</div>
);
}

Confused as to how the template render action is called

I am trying to make the songs in a playlist appear on screen each time a user enters a song of choice. I have the following action to insert the song that they chose into the database:
Template.search_bar.events({
'keypress #query' : function (evt,template) {
// template data, if any, is available in 'this'
if (evt.which === 13){
var url = template.find('#query').value;
$("#query").val('');
$('#playlist_container').animate({scrollTop: $('#playlist_container')[0].scrollHeight});
Template.list.search_get(url,0); //insert records into the database
}
}
});
Template.list.search_get inserts the record into the database:
Meteor.call('update_record',Template.list.my_playlist_id, song, function(err,message){});
on the server side, I am pushing records into my database with the following format:
update_record: function(sessID, songObj){
Links.update({sess: sessID}, {$push: {songs: {song_title: songObj["title"], videoId: songObj["video_id"], thumbnail: songObj["thumbnail"], index: songObj["index"]}}});
},
basically all my records have the format of:
{_id:,
sess:,
songs: [{song_title:,
videoId:,
thumbnail:,
index:},
{song_title:,
videoId:,
thumbnail:,
index:},...]
}
An array of song objects inside the songs field of the record. What I am trying to do is each time a user hits the search button, make that new song appear in the list. I am not sure how many times the render function gets called or how template renders a database object in hmtl. Currently i have the following html template for my list:
<template name="list">
<div id="playlist_container">
<ul id="playlist">
{{#each my_playlist.songs}}
{{> track}}
{{/each}}
</ul>
</div>
I believe my_playlist should call the following action on the client:
Template.list.my_playlist = function(){
console.log("myplaylist is called");
return Links.findOne({sess: Template.list.my_playlist_id});
}
It should return an object which contains an array of song object, for which i iterate through in #each my_playlist.songs, which should render each of the following track template:
<template name="track">
<li id="{{index}}" class="list_element">
<div class="destroy"> </div>
<div class="element_style">{{song_title}}</div>
</li>
</template>
However, upon successful insertion of record, i am not seeing the new song title appear. Any suggestions on how I might approach this?
This code is the problem.
Template.list.my_playlist = function(){
return Links.findOne({sess: Template.list.my_playlist_id});
}
Template.list.my_playlist_id never updates, and thus, the new template never renders.
Try this approach.
if (Meteor.isServer) {
Meteor.methods({
update_record: function(sessID, songObj){
// update Links
return Links.findOne({sess: sessID});
}
});
} else {
Meteor.call('update_record',Template.list.my_playlist_id, song, function(err, song){
Session.set('playlist', song);
});
Template.list.my_playlist = function(){
return Session.get('playlist');
}
}

Mvvm with knockout : array binding and changing inner objects state

I have an array in my View Model. Items of this array are objects of Person that has two properties. when I bind this to a template it's okay. but when I change the state of one of the properties it does not reflect in UI.
what did I do wrong ?
<script type="text/html" id="person-template">
<p>Name: <span data-bind="text: name"></span></p>
<p>
Is On Facebook ?
<input type="checkbox" data-bind="checked: IsOnFacebook" />
</p>
</script>
<script type="text/javascript">
var ppl = [
{ name: 'Pouyan', IsOnFacebook: ko.observable(true) },
{ name: 'Reza', IsOnFacebook: ko.observable(false) }
];
function MyViewModel() {
this.people = ko.observableArray(ppl),
this.toggle = function () {
for (var i = 0; i < ppl.length; i++) {
ppl[i].IsOnFacebook = false;
}
}
}
ko.applyBindings(new MyViewModel());
</script>
when I press the button I want to make changes in People.IsOnFacebook property. the changes will be made successfully but the UI does not show.
You should call it like a function. Like:
ppl[i].IsOnFacebook(false);
This because the ko.observable() returns a function. It's not a property you call anymore but a function call. So in the background they will update your UI. To retreive a property that is observable. You should also use the function call.
Please see this tutorial: http://learn.knockoutjs.com/#/?tutorial=intro