I am having trouble passing data between controllers using a service. What i want to happen is when send data is clicked the data inputted into the text field should be populated in the Results controller. However nothing shows
Home.html:
<html>
<body>
<ion-header-bar class="bar-dark">
<h1 class="title"></h1>
</ion-header-bar>
<ion-view view-title="Home">
<ion-content ng-controller="StockUpdateCtrl">
<div class="list">
<ion-refresher pulling-text="Pull to Refresh" on-refresh="doRefresh()"></ion-refresher>
<div>{{text}}</div>
<input type='text' ng-model='text' />
<button type='button' ng-click='send()'>Send Data</button>
<div ng-controller='ResultsController'>
<div>
<h4>Ctrl2</h4>
<div>{{text}}</div>
</div>
</div>
</ion-content>
</ion-view>
</body>
</html>
HomeController.js:
var app = angular.module('starter', ['ionic'])
.run(function ($ionicPlatform) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if (window.StatusBar) {
StatusBar.styleDefault();
}
});
})
/*
* Data Service
* Service used to pass data between controllers
*/
app.factory('dataShare', function(){
var service = {};
service.data = false;
service.sendData = function(data){
this.data = data;
$rootScope.$broadcast('data_shared');
};
service.getData = function(){
return this.data;
};
return service;
});
/*
* Stock Update Controller
* Gets user input and then performs calculations to prepare to be displayed
*
*/
app.controller("StockUpdateCtrl", function ($scope, $http, dataShare) {
$scope.text = 'Hey';
$scope.send = function(){
dataShare.sendData($scope.text);
};
});
ResultsController.js:
* Resultse Controller
* Displays the results
*
*/
app.controller("ResultsController",function ($scope, dataShare) {
$scope.text = '';
$scope.$on('data_shared',function(){
var text = dataShare.getData();
$scope.text = text;
});
});
Haha figured it out. Yes it turned out it was the way i was calling the results page... I had to remove form tag
<form action="index.html#/results" ng-controller="StockUpdateCtrl">
and change my button to call the results page as such
<button type="submit" ui-sref="results" ng-click="calculate()" class="button button-block button-balanced">
Related
I recently started learning LWC. I am working on a requirement in LWC where on component initialization I need to use record Id to get value of numeric field from same object/record, check if it is 100 and display a message on component html. Note - this LWC is used as a quick action of type Screen Action. Following is current implementation which works at times and doesn't (not sure if it is a cache issue).
LWCComponent.html
<template>
<!-- modal start -->
<template if:true={isShowModal}>
<lightning-quick-action-panel header="New Record">
<div class="slds-modal__content modalBodySpinner" style="width: 100%;" id="modal-content-id-1">
<template if:true={isLoaded}>
<lightning-spinner alternative-text="Loading" size="medium" class="spinnerClass"></lightning-spinner>
</template>
<div slot>
<lightning-layout>
<lightning-layout-item size="6" padding="around-small">
<lightning-input label="Record Name" type = "text" value={recordName}></lightning-input>
</lightning-layout-item>
</lightning-layout>
</div>
<template if:true={recordId}></template>
<template if:true={showForm}>
<lightning-input label="Record ID" type="text"></lightning-input>
</template>
<template if:false={showForm}>
<p class="warningP">{restrictMessage}</p>
</template>
<br/>
</div>
<div slot="footer">
<lightning-button variant="neutral" label="Cancel" onclick={closeModal}></lightning-button>
</div>
</lightning-quick-action-panel>
</template>
<!-- modal end -->
</template>
LWCComponent.js
import { LightningElement, track, api, wire } from 'lwc';
import { getRecord, getFieldValue } from "lightning/uiRecordApi";
import NAME from "#salesforce/schema/CustomObject__c.Name";
import CAPACITY from "#salesforce/schema/CustomObject__c.Capacity__c";
import { CloseActionScreenEvent } from 'lightning/actions';
import restrictMessage from "#salesforce/label/c.RestrictWarning";
const fields = [NAME, CAPACITY];
export default class NewClass extends LightningElement {
#track isShowModal = true;
#track showForm = true;
#api recordId;
#track isLoaded = true;
#api capacityVal;
#track warningMsg;
#track retrievedRecordId = false;
#wire(getRecord, {
recordId: "$recordId",
fields
})
custObjRec;
get recordName() {
return getFieldValue(this.custObjRec.data, NAME);
}
renderedCallback() {
if(!this.retrievedRecordId && this.recordId) {
this.retrievedRecordId = true;
this.capacityVal = getFieldValue(this.custObjRec.data, CAPACITY)
if(this.capacityVal == 100) {
this.showForm = false;
this.warningMsg = restrictMessage;
this.isLoaded = false;
}
else {
this.isLoaded = false;
}
}
else {
this.isLoaded = false;
}
}
// method to close modal pop up
closeModal() {
this.dispatchEvent(new CloseActionScreenEvent());
}
}
Any suggestions to make this better?
I'm trying to do address validation using google api within a Meteor Platform. What's the best way to achieve that?
I tried this but I'm facing a lot of trouble. Is there a more structured way to do that in Meteor?
<div class="page">
<div id="main">
<form id="MyForm" name="MyForm" action="form.html">
<div id="map_canvas" style="float: right; height: 200px; width: 400px;"></div>
<div>
<label for="Name">
Name</label>
<input id="Name" name="Name" type="text" />
</div>
<div>
<label for="FullAddress">
Address</label>
<textarea id="FullAddress" name="FullAddress" cols="40" rows="5" class="fulladdressvalidator"></textarea>
</div>
<div>
<input id="Submit" name="Submit" value="Submit" type="button" />
</div>
</form>
</div>
</div>
<script type="text/javascript">
// The following code show execute only after the page is fully loaded
$(document).ready(function () {
if ($('#MyForm').exists()) {
// Enable jQuery Validation for the form
$("#MyForm").validate({ onkeyup: false });
// Add validation rules to the FullAddress field
$("#FullAddress").rules("add", {
fulladdress: true,
required: true,
messages: {
fulladdress: "Google cannot locate this address."
}
});
// This function will be executed when the form is submitted
function FormSubmit() {
$.submitForm = true;
if (!$('#MyForm').valid()) {
return false;
} else {
if ($("#FullAddress").data("IsChecking") == true) {
$("#FullAddress").data("SubmitForm", true);
return false;
}
alert('Form Valid! Submit!');
// return true; // Uncomment to submit the form.
return false; // Supress the form submission for test purpose.
}
}
// Attach the FormSubmit function to the Submit button
if ($('#Submit').exists()) {
$("#Submit").click(FormSubmit);
}
// Execute the ForumSubmit function when the form is submitted
$('#MyForm').submit(FormSubmit);
}
});
// Create a jQuery exists method
jQuery.fn.exists = function () { return jQuery(this).length > 0; }
// Position the Google Map
function Map(elementId, geolocation) {
var myOptions = {
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById(elementId), myOptions);
map.setCenter(geolocation);
}
// FullAddress jQuery Validator
function FullAddressValidator(value, element, paras) {
// Convert the value variable into something a bit more descriptive
var CurrentAddress = value;
// If the address is blank, then this is for the required validator to deal with.
if (value.length == 0) {
return true;
}
// If we've already validated this address, then just return the previous result
if ($(element).data("LastAddressValidated") == CurrentAddress) {
return $(element).data("IsValid");
}
// We have a new address to validate, set the IsChecking flag to true and set the LastAddressValidated to the CurrentAddress
$(element).data("IsChecking", true);
$(element).data("LastAddressValidated", CurrentAddress);
// Google Maps doesn't like line-breaks, remove them
CurrentAddress = CurrentAddress.replace(/\n/g, "");
// Create a new Google geocoder
var geocoder = new google.maps.Geocoder();
geocoder.geocode({ 'address': CurrentAddress }, function (results, status) {
// The code below only gets run after a successful Google service call has completed.
// Because this is an asynchronous call, the validator has already returned a 'true' result
// to supress an error message and then cancelled the form submission. The code below
// needs to fetch the true validation from the Google service and then re-execute the
// jQuery form validator to display the error message. Futhermore, if the form was
// being submitted, the code below needs to resume that submit.
// Google reported a valid geocoded address
if (status == google.maps.GeocoderStatus.OK) {
// Get the formatted Google result
var address = results[0].formatted_address;
// Count the commas in the fomatted address.
// This doesn't look great, but it helps us understand how specific the geocoded address
// is. For example, "CA" will geocde to "California, USA".
numCommas = address.match(/,/g).length;
// A full street address will have at least 3 commas. Alternate techniques involve
// fetching the address_components returned by Google Maps. That code looks even more ugly.
if (numCommas >= 3) {
// Replace the first comma found with a line-break
address = address.replace(/, /, "\n");
// Remove USA from the address (remove this, if this is important to you)
address = address.replace(/, USA$/, "");
// Check for the map_canvas, if it exists then position the Google Map
if ($("#map_canvas").exists()) {
$("#map_canvas").show();
Map("map_canvas", results[0].geometry.location);
}
// Set the textarea value to the geocoded address
$(element).val(address);
// Cache this latest result
$(element).data("LastAddressValidated", address);
// We have a valid geocoded address
$(element).data("IsValid", true);
} else {
// Google Maps was able to geocode the address, but it wasn't specific
// enough (not enough commas) to be a valid street address.
$(element).data("IsValid", false);
}
// Otherwise the address is invalid
} else {
$(element).data("IsValid", false);
}
// We're no longer in the midst of validating
$(element).data("IsChecking", false);
// Get the parent form element for this address field
var form = $(element).parents('form:first');
// This code is being run after the validation for this field,
// if the form was being submitted before this validtor was
// called then we need to re-submit the form.
if ($(element).data("SubmitForm") == true) {
form.submit();
} else {
// Re-validate this property so we can return the result.
form.validate().element(element);
}
});
// The FullAddress validator always returns 'true' when initially called.
// The true result will be return later by the geocode function (above)
return true;
}
// Define a new jQuery Validator method
$.validator.addMethod("fulladdress", FullAddressValidator);
</script>
To achieve this in a Meteor way you should put your html into a template:
<template name="tplExample">
<div class="page">
{{somedata}}
</div>
</template>
Then you can write your events and helpers for this template in a .js file:
Template['tplExample'].events({
//this scopes just elements from tplExample
"submit #Myform": function(e){/*handle the event*/ }
})
Template['tplExample'].rendered = function(){
// The following code show execute only after the page is fully loaded
}
Template['tplExample'].helpers({
//this scopes just elements from tplExample
some_data: function(){ /*handle data validation*/}
})
Take a look here https://www.discovermeteor.com/blog/a-look-at-a-meteor-template/
I have a form in weather that would have had the condition User add as many lines he needs. He clicks a button and an input is added below the other.
I can do this using jQuery, but I would prefer to use the resources of Meteor. Is it possible to do?
Yes it is, here is an example from one of my apps using the underscore package
In the main template:
<template name="ask">
{{#each answerArray}}
{{>answer}}
{{/each}}
<button id="addItem">Add item</button>
</template>
<template name="answer">
<div class="input-group pt10">
<input class="form-control answer" maxlength="30" placeholder="Answer (max 30 chars)" name="answer" />
<span class="input-group-btn">
<button class="btn btn-danger delButton" id="{{id}}" data-id="{{id}}" type="button">Delete</button>
</span>
</div>
</template>
In the js file:
Template.ask.created = function () {
Session.set('action', 'ask');
answerArray = [ //adding at least two items but it could also be empty
{
id: Random.id(), //using this to give an unique id to the control
value: ''
},
{
id: Random.id(),
value: ''
}
];
Session.set('answerArr', answerArray);
}
And the click event:
Template.ask.events = {
'click #addItem': function () {
var answerArray = Session.get('answerArr');
answerArray.push({
id: Random.id() //just a placeholder, you could put any here
});
Session.set('answerArr', answerArray);
}
}
And finally the helper:
Template.ask.helpers({
answerArray: function () {
var answerArray = Session.get("answerArr")
while (answerArray.length < 2) { //i chose to have it between 2 and 6, you can remove these
answerArray.push({
id: Random.id()
})
}
while (answerArray.length > 6) { // maximum
answerArray.pop();
}
Session.set('answerArr', answerArray);
return answerArray;
}
}
This will reactively increase the number of inputs. After that, if you want to process the inputs you could do the following, on a submit form event or button click:
'click #saveQ': function (e) {
e.preventDefault();
var arr = [];
_.each($('.answer'), function (item) {
if ($(item).val() != '')
arr.push({
answer: $(item).val(), //this you customize for your own purposes
number: 0
})
});
And also if you want to delete an input from the page you can use:
Template.answer.events = {
'click .delButton': function (e) {
var thisId = $(e.target).attr("id");
var answerArray = Session.get('answerArr');
var filteredArray = _.filter(answerArray, function (item) {
return item.id != thisId;
});
Session.set('answerArr', filteredArray);
}
}
I know this is long, but it appears something specific in what I'm doing in the more complex scenario is causing the issue. All simple examples I attempt work fine.
I have an application setup using angularjs and laravel four.
The home page is rendered via laravel routing:
Route::get('/', function() {
return View::make('app');
});
app.php returns the website skeleton with the following structure:
<!doctype html>
<html lang="en" ng-app="app">
<head>
<script src="/js/angular.js"></script>
<script src="/js/angular-sanitize.js"></script>
<script src="/js/angular-ui-router.js"></script>
<script src="/js/app.js"></script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" ng-controller="NavController">
<div class="navbar-inner">
<div class="container-fluid">
<button class="button pull-right" ng-click="logout()">Logout</button>
<p class="navbar-text pull-right" >
Logged in as {{ currentUser.email }} id is : {{ currentUser.userId }}
</p>
</div>
</div>
</div>
</div>
<div class="row-fluid offset05">
<div id="view" ng-view></div>
</div>
</div>
</body>
</html>
basics of app.js:
var app = angular.module("app", ['ngSanitize','ui.state','ui.bootstrap']);
the user is initially routed to a login template, at which point a LoginController updates the user information which resides in UserService.
app.controller("LoginController", function($scope,$rootScope, $location, AuthenticationService, UserService) {
$scope.credentials = { email: "", password: "" };
$scope.login = function() {
AuthenticationService.login($scope.credentials).success(function() {
$location.path('/home');
});
};
});
Authentication Service updates the UserService variable appropriately:
app.factory("AuthenticationService", function($rootScope, $http, $sanitize, SessionService, FlashService, CSRF_TOKEN, UserService) {
return {
login: function(credentials) {
var login = $http.post("/auth/login", sanitizeCredentials(credentials));
login.success(function(data){
UserService.currentUser = data.user;
});
return login;
}
});
NavController (controller for navigation bar seen above) binds its scope to the UserService.currentUser.
app.controller("NavController",function($scope, UserService, AuthenticationService,$location){
$scope.currentUser = UserService.getCurrentUser();
});
Relevant parts of UserService:
app.factory("UserService", function($http){
var _currentUser = {}
return{
currentUser: _currentUser,
getCurrentUser: function() {
return _currentUser;}
};
});
When the user logs in, their user email and userid should appear in the navigation bar.
If I create an example which strictly uses javascript/html there are no issues with the binding.
With the mechanics/structure mentioned above, the navbar does not respond to the changes in the UserService current user variable until the entire page is reloaded.
After the user logs in, I can verify that the UserController and UserService both update the currentUser appropriately. In spite of this, the NavController will not reflect the updated user unless I reload the whole page.
I assume this is becuase the NavController is now re-running with the updated information, but why isn't normal binding working?
I guess this has something to do with the fact that the navigation bar is loaded via php.
My question is how can I either:
a) make the binding via the service work appropriately
or
b) reload the NavController when necessary (post login/logout)
There are a couple ways you can handle this.
You can bind your $scope to the service itself, in which case any changes to that model will be picked up automatically
You can observe changes to the service using $watch
In this example, you can see both techniques: (http://plnkr.co/edit/bhBXMr?p=preview):
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, AuthenticationService, UserService) {
$scope.user = UserService;
$scope.login = function(){
AuthenticationService.login();
}
// watch the service for changes to currentUser
$scope.$watch(function(){
return UserService.currentUser;
}, function(currentUser){
$scope.currentUser = currentUser;
}, true);
});
app.service('AuthenticationService', function($http, UserService){
return {
login: function(){
$http.get('data.json').success(function(data){
UserService.currentUser = data;
});
}
};
});
app.service('UserService', function($rootScope){
return {
currentUser: null
};
});
HTML Markup:
<body ng-controller="MainCtrl">
<button ng-click="login()">Login</button>
<div>Current User: {{user.currentUser}}</div>
<!-- from the watch -->
<div>Current User: {{currentUser}}</div>
</body>
I have some code similar to the following:
In myapp.html
<template name="problems">
<div class="problems">
{{#each problems}}
{{> problem}}
{{/each}}
</div>
</template
<template name="problem">
<div class="problem">
<div class="problem-text" id={{_id}}>{{text}}</div>
</div>
</template>
In myapp.js
Template.problem.events = {
'click .problem-text' : function () {
var user_id = Session.get('user_id');
// how to get problem_id of clicked item?
Router.gotoProblem(user_id, problem_id);
}
};
In this situation I want to get the id of the that matched .problem-text and was clicked.
I would like to know the "object" that generated the event? How do I do this?
The selected answer for this question will ONLY get the _id , and that too if _id is used in templates.
So better use, event.target, that will give COMPLETE object. So that can be used with jQuery or MooTools.
Template.top_nav.events({
'mousedown, .nav li a': function(evt){
console.log('the class of object that was clicked is ' + $(evt.target).attr("class"));
}
})
Try this:
Template.problem.events = {
'click .problem-text' : function () {
var user_id = Session.get('user_id');
// how to get problem_id of clicked item?
Router.gotoProblem(user_id, this._id);
}
};
You can access the problem object with this reference:
Template.problem.events = {
/**
* Handles problem text click.
* #param {jQuery.Event} event
*/
'click .problem-text' : function (event) {
/*
Here this is a reference to the problem object
and you have jQuery.Event object as the event argument.
*/
console.log(this, arguments);
var user_id = Session.get('user_id');
var problem = this;
// how to get problem_id of clicked item?
Router.gotoProblem(user_id, /** problem_id */problem._id);
}
};