Angular-Material cannot close $mdDialog with a button - modal-dialog

I cannot get the button to close the Modal, or for the click outside of the modal to close the window. I do not get an error and I have the same problem using a simple Controller, or the ControllerAs syntax.
My sample Modal code:
angular.module('MyApp').controller('TestCtrl', ["$scope", "$mdDialog",
function($scope, $mdDialog) {
var alert;
$scope.showInfoModal = showGameInfoModal;
// Internal method
function showGameInfoModal(schedule) {
$scope.schedule = schedule;
alert = $mdDialog.show({
clickOutsideToClose: true,
disableParentScroll: true,
escapeToClose: true,
scope: $scope, // use parent scope in template
preserveScope: true, // do not forget this if use parent scope
// Since GreetingController is instantiated with ControllerAs syntax
// AND we are passing the parent '$scope' to the dialog, we MUST
// use 'vm.<xxx>' in the template markup
templateUrl: 'Modules/Test/Test2.html',
controller: function DialogController($scope, $mdDialog) {
$scope.closeDialog = function() {
$mdDialog.hide();
};
},
controllerAs: 'dc'
});
}
}
]);
And then the Partials HTML:
<md-content>
<md-toolbar flex layout="row" layout-align="center">
<h5>{{schedule.GameDate</h5>
</md-toolbar>
<section layout="column" layout-align="center">
<div ng-init="coords={latitude: false, longitude: false}">
<gm-map options="{center: [43.6576221, -79.607505], zoom: 13, mapTypeId: google.maps.MapTypeId.ROADMAP}">
<gm-marker options="{position: [43.6576221, -79.607505], draggable: false}">
<gm-infowindow options="{content: 'Max Ward 2'}"></gm-infowindow>
</gm-marker>
</gm-map>
</div>
</section>
<section>
<div class="md-actions">
<md-button ng-click="dc.closeDialog()" class="md-fab md-fab-bottom-right md-mini" aria-label="Close"><ng-md-icon icon="close"></ng-md-icon></md-button>
</div>
</section>
</md-content>

In searching for more examples, I found that I missed the <md-dialog> tags.
<md-dialog aria-label="Game Info and Map">
<md-content>
<md-toolbar flex layout="row" layout-align="center">
...
</md-toolbar>
</md-content>
</md-dialog>

Related

Building error at #parcel/transformer-js "cannot access a scoped thread local variable without..."

I have a project with Parcel and Preact (to implement Algolia Autocomplete Search) and I suddenly got an error while npx parcel build theme/app-home.tsx --log-level verbose.
It worked before and I'm working with git but I can't found what changed to break the build.
The error:
Building app-home.tsx...
thread '<unnamed>' panicked at 'cannot access a scoped thread local variable without calling `set` first', /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/scoped-tls-1.0.0/src/lib.rs:168:9
🚨 Build failed.
#parcel/transformer-js: cannot access a scoped thread local variable without calling `set` first
Error: cannot access a scoped thread local variable without calling `set` first
at Object.transform (/Users/cozarkd/Documents/GitHub/projectX/node_modules/#parcel/transformer-js/lib/JSTransformer.js:365:31)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Transformation.runTransformer (/Users/cozarkd/Documents/GitHub/projectX/node_modules/#parcel/core/lib/Transformation.js:617:5)
at async Transformation.runPipeline (/Users/cozarkd/Documents/GitHub/projectX/node_modules/#parcel/core/lib/Transformation.js:366:36)
at async Transformation.runPipelines (/Users/cozarkd/Documents/GitHub/projectX/node_modules/#parcel/core/lib/Transformation.js:244:40)
at async Transformation.run (/Users/cozarkd/Documents/GitHub/projectX/node_modules/#parcel/core/lib/Transformation.js:170:19)
at async Child.handleRequest (/Users/cozarkd/Documents/GitHub/projectX/node_modules/#parcel/workers/lib/child.js:217:9)
The tsx file:
/** #jsx h */
import {
autocomplete,
AutocompleteComponents,
getAlgoliaResults,
} from '#algolia/autocomplete-js';
import algoliasearch from 'algoliasearch';
import { h, Fragment } from 'preact';
const appId = 'theappid';
const apiKey = 'theapikey';
const searchClient = algoliasearch(appId, apiKey);
/* const querySuggestionsPlugin = createQuerySuggestionsPlugin({
searchClient,
indexName: 'database_query_suggestions',
getSearchParams() {
return {
hitsPerPage: 5,
};
},
}); */
autocomplete({
// debug: true,
container: '#autocomplete',
placeholder: 'Escribe aquí, sin miedo',
openOnFocus: false,
defaultActiveItemId: 0,
autoFocus: true,
getSources({ query }) {
return [
{
sourceId: 'plantag',
getItemUrl({ item }) {
return item.url;
},
getItems() {
return getAlgoliaResults({
searchClient,
queries: [
{
indexName: 'database',
query,
params: {
hitsPerPage: 10,
clickAnalytics: true,
attributesToSnippet: [
'name:10',
'nombre'
],
snippetEllipsisText: '…',
},
},
],
});
},
templates: {
item({ item, components }) {
return <ProductItem hit={item} components={components} />;
},
noResults() {
return 'No hay plantas coincidentes :(';
},
},
},
];
},
// Default Navigator API implementation
navigator: {
navigate({ itemUrl }) {
window.location.assign(itemUrl);
},
navigateNewTab({ itemUrl }) {
const windowReference = window.open(itemUrl, '_blank', 'noopener');
if (windowReference) {
windowReference.focus();
}
},
navigateNewWindow({ itemUrl }) {
window.open(itemUrl, '_blank', 'noopener');
},
},
});
function ProductItem({ hit, components }: ProductItemProps) {
return (
<div className="c-single-result">
<a href={hit.url} className="aa-ItemLink">
<div className="l-flex-container">
<div className="aa-ItemContent">
<div className="aa-ItemContentBody">
<div className="aa-ItemContentTitle">
<components.Snippet hit={hit} attribute="nombre" />
</div>
</div>
<div className="aa-ItemContentSubtitle">
<components.Snippet hit={hit} attribute="name" />
</div>
<div className="aa-ItemActions">
<button
className="aa-ItemActionButton aa-DesktopOnly aa-ActiveOnly"
type="button"
title="Select"
style={{ pointerEvents: 'none' }}
>
<svg viewBox="0 0 30 27" width="30" height="27" fill="currentColor">
<path d="M10.0611 23.8881C10.6469 24.4606 10.6469 25.389 10.0611 25.9615C9.47533 26.5341 8.52558 26.5341 7.9398 25.9615L0.441103 18.632C0.4374 18.6284 0.433715 18.6248 0.430051 18.6211C0.164338 18.3566 0.000457764 17.994 0.000457764 17.594C0.000457764 17.3952 0.0409356 17.2056 0.114276 17.0328C0.187475 16.8598 0.295983 16.6978 0.439798 16.5572L7.9398 9.22642C8.52558 8.65385 9.47533 8.65385 10.0611 9.22642C10.6469 9.79899 10.6469 10.7273 10.0611 11.2999L5.12178 16.1278H13.5005C20.9565 16.1278 27.0005 10.2202 27.0005 2.93233V1.46616C27.0005 0.656424 27.672 -1.90735e-06 28.5005 -1.90735e-06C29.3289 -1.90735e-06 30.0005 0.656424 30.0005 1.46616V2.93233C30.0005 11.8397 22.6134 19.0602 13.5005 19.0602H5.12178L10.0611 23.8881Z"/>
</svg>
</button>
</div>
</div>
<div className="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
<img src={hit.image} alt={hit.name} width="40" height="40" />
</div>
</div>
</a>
</div>
);
}
I can provide more info if needed. Did some search but I couldn't find anything related to Parcel with that error.
I'm able to repro the problem, and it looks like this might be a bug in parcel related to the JSX pragma at the top of the file (see github discussion here). If you remove this line, it should compile fine:
/** #jsx h */ <--delete this line.
Parcel will automatically detect which JSX pragma to use for your project based on finding preact as a dependency in package.json (see documentation). You can also manually control it with a tsconfig.json file like this:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
(This bug should probably still be fixed, but hopefully this is enough to help you work around it. I filed a github issue here).

react-google-maps StandaloneSearchBox set specific country restriction?

I am trying to set a specific country restriction using react-google-maps StandaloneSearchBox.
I have tried componentRestrictions, but I'm not sure how to use it.
Sharing my code below:
export const AutoCompleteSearchBox = compose(
withProps({
googleMapURL:googleMapUrl,
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px`, top:'3px' }} />,
}),
lifecycle({
componentWillMount() {
const refs = {}
this.setState({
types: ['(regions)'],
componentRestrictions: {country: "bd"},
onSearchBoxMounted:ref =>{ refs.searchBox = ref; },
onPlacesChanged:()=>{
const places = refs.searchBox.getPlaces();
this.props.onPlacesChanged(places);
},
})
const options = {
types: ['(regions)'],
componentRestrictions:{ country: 'bd' }
}
},
}),
withScriptjs
)`(props =>
<div data-standalone-searchbox="">
<StandaloneSearchBox
ref={props.onSearchBoxMounted}
bounds={props.bounds}
onPlacesChanged={props.onPlacesChanged}
controlPosition={ window.google.maps.ControlPosition.TOP_LEFT}
>
<TextField
className={props.inputClass}
placeholder={props.inputPlaceholder}
label={props.inputLabel}
name={props.inputName}
value={props.inputValue}
onChange={props.inputOnChange}
helperText={props.inputHelperText}
error={props.inputError}
/>
</StandaloneSearchBox>
</div>
);`
How can I solve this problem?
You can't add such restrictions for the SearchBox results, but you can specify the area towards which to bias query predictions. Predictions are biased towards, but not restricted to, queries targeting these bounds.
If you want to show only specific places, then you can Google Place Autocomplete feature. For it you don't event need to use additional React libraries for Google Maps. Here's the example:
import React, { Component } from 'react';
import Script from 'react-load-script'
class LocationMap extends Component {
handleScriptLoad() {
const inputEl = document.getElementById('address-input');
/*global google*/
var options = {
//types: ['address'],
componentRestrictions: {country: 'by'}
};
this.autocomplete = new google.maps.places.Autocomplete(inputEl, options);
this.autocomplete.addListener('place_changed', this.handlePlaceSelect.bind(this));
}
handlePlaceSelect() {
console.log(this.autocomplete.getPlace());
}
render() {
return (
<section>
<Script
url="https://maps.googleapis.com/maps/api/js?key=API_KEY&v=3.33&libraries=places&language=en&region=US"
onLoad={this.handleScriptLoad.bind(this)}
/>
<div className="form-group">
<label htmlFor="address-map">Enter address</label>
<input type="text"
autoComplete="new-password"
className="form-control"
id="address-input"
name="address"/>
</div>
</section>
);
}
}
export default LocationMap;
Don't forget to add react-load-script package: npm i react-load-script --save

UI5: Check if a control is currently rendered and visible

In a SAPUI5 application I would like to update the content of a control (e. g. a tile) only when this is currently visible to the user.
I created a function like this:
updatePage: function() {
for (var i = 0; i < this.myTiles.length; i++) {
if (this.myTiles[i].updater) {
this.myTiles[i].updater();
}
}
setTimeout(this.updatePage.bind(this), 10000);
},
.. where the updater is a custom function I added to the tiles that is in charge to update their content.
The problem is: I want to check if the tile is currently visible to the user (i. e. is not in a page or in a tab that is not currently selected, but was rendered previously).
Is there a way to achieve this using object properties? Do I need to manage it manually?
You can utilize jQuery do achieve that.
// determine whether your control is still part of the active DOM
jQuery.contains(document, yourControl.$());
// determine whether your control is visible (in terms of CSS)
yourControl.$().is(':visible');
// combined
MyControl.prototype.isVisible = function() {
return this.$().length && // has rendered at all
jQuery.contains(document, this.$()) && // is part of active DOM
this.$().is(':visible'); // is visible
};
Elements could still be invisible to the user by being:
out of the viewport
visibility: hidden
opacity: 0
...
Also check this answer
BR
Chris
http://api.jquery.com/jQuery.contains/
http://api.jquery.com/visible-selector/
The web API IntersectionObserver is now supported by all major browsers, including Safari.
Basic syntax
const myObserver = new IntersectionObserver(callback/*, settings?*/);
myObserver.observe(domElement);
Demo
Below is a demo with UI5. Run the snippet and try to scroll there. The page title changes depending on the visibility of the target element:
globalThis.onUI5Init = () => sap.ui.require([
"sap/ui/core/mvc/XMLView",
"sap/ui/model/json/JSONModel",
"sap/ui/core/mvc/Controller",
], async (XMLView, JSONModel, Controller) => {
"use strict";
const control = await XMLView.create({
definition: document.getElementById("myxmlview").textContent,
models: new JSONModel({ "ratio": 0 }),
controller: new (Controller.extend("demo.MyController", {
onInit: function () {
this.intersectionObserver = new IntersectionObserver(entries => {
const targetEl = this.byId("myBox").getDomRef();
const entry = entries.find(entry => entry.target === targetEl);
const model = this.getView().getModel();
model.setProperty("/ratio", entry && entry.intersectionRatio);
});
},
onBeforeRendering: function () {
this.intersectionObserver.disconnect();
},
onAfterRendering: function () {
const targetEl = this.byId("myBox").getDomRef();
this.intersectionObserver.observe(targetEl);
},
onExit: function () {
this.intersectionObserver.disconnect();
this.intersectionObserver = null;
},
}))(),
});
control.placeAt("content");
});
<script id="sap-ui-bootstrap"
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.core,sap.m"
data-sap-ui-async="true"
data-sap-ui-theme="sap_horizon"
data-sap-ui-oninit="onUI5Init"
data-sap-ui-resourceroots='{ "demo": "./" }'
data-sap-ui-compatVersion="edge"
data-sap-ui-excludejquerycompat="true"
data-sap-ui-xx-waitForTheme="init"
></script>
<script id="myxmlview" type="text/xml">
<mvc:View controllerName="demo.MyController"
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns="sap.m"
displayBlock="true">
<App>
<Page title="Tile visible: {= ${/ratio} > 0}">
<FlexBox renderType="Bare"
height="360vh"
justifyContent="Center"
alignItems="Center">
<core:Icon id="myBox"
src="sap-icon://color-fill"
size="5rem"
color="Critical"/>
</FlexBox>
</Page>
</App>
</mvc:View>
</script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>

How to submit radio button value + additional info about the form to Redux

This is a bit longwinded so I'll do my best to explain clearly.
I'm making a simple poll app and on the home page is an array of polls where you can vote on each poll.
Each poll is on a card and there will be different radio buttons representing the different voting options for that poll.
I'm trying to set up a form for each poll which contains radio button inputs for each of the different options and push that onSubmit to an action creator.
However, I would also like to pass that title of the poll as well as an argument to the action creator so that I can create a single action creator that will help me submit the votes for all the polls. Something like submitVote(title, option).
Here is my polls page:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Loading from '../Loading';
class MyPolls extends Component {
constructor(props) {
super(props);
this.state = {
skip: 0,
isLoading: true,
isLoadingMore: false,
value: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.props.fetchMyPolls(this.state.skip)
.then(() => {
setTimeout(() => {
this.setState({
skip: this.state.skip + 4,
isLoading: false
});
}, 1000);
});
}
sumVotes(acc, cur) {
return acc.votes + cur.votes
}
loadMore(skip) {
this.setState({ isLoadingMore: true });
setTimeout(() => {
this.props.fetchMyPolls(skip)
.then(() => {
const nextSkip = this.state.skip + 4;
this.setState({
skip: nextSkip,
isLoadingMore: false
});
});
}, 1000);
}
handleSubmit(e) {
e.preventDefault();
}
handleChange(event) {
console.log(event.target.value);
this.setState({ value: event.target.value });
}
renderPolls() {
return this.props.polls.map(poll => {
return (
<div className='card' key={poll._id} style={{ width: '350px', height: '400px' }}>
<div className='card-content'>
<span className='card-title'>{poll.title}</span>
<p>Total votes: {poll.options.reduce((acc, cur) => { return acc + cur.votes }, 0)}</p>
<form onSubmit={this.handleSubmit}>
{poll.options.map(option => {
return (
<p key={option._id}>
<input
name={poll.title}
className='with-gap'
type='radio'
id={option._id}
value={option.option}
onChange={this.handleChange}
/>
<label htmlFor={option._id}>
{option.option}
</label>
</p>
)
})}
<button
type='text'
className='activator teal btn waves-effect waves-light'
style={{
position: 'absolute',
bottom: '10%',
transform: 'translateX(-50%)'
}}
>
Submit
<i className='material-icons right'>
send
</i>
</button>
</form>
</div>
<div className='card-reveal'>
<span className='card-title'>{poll.title}
<i className='material-icons right'>close</i>
</span>
<p>
dsfasfasdf
</p>
</div>
</div>
)
})
}
render() {
return (
<div className='center-align container'>
<h2>My Polls</h2>
{this.state.isLoading ? <Loading size='big' /> :
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-evenly', alignItems: 'center', alignContent: 'center' }}>
{this.renderPolls()}
</div>}
<div className='row'>
{this.state.isLoadingMore ? <Loading size='small' /> :
<button
className='btn red lighten-2 wave-effect waves-light' onClick={() => this.loadMore(this.state.skip)}>
Load More
</button>}
</div>
</div>
);
}
}
function mapStateToProps({ polls }) {
return { polls }
}
export default connect(mapStateToProps, actions)(MyPolls);
Demo of the app so far: https://voting-app-drhectapus.herokuapp.com/
(use riverfish#gmail.com and password 123 to login).
Github repo: https://github.com/drhectapus/Voting-App
I'd like to program it so that when form is submitted via this.handleSubmit, the handleSubmit function can take 2 arguments, title and option and pass that onto an action creator in redux.
How do I do this?
It's a little difficult to understand everything going on here, but I get the sense that your main goal is to pass two args to this.handleSubmit. You may instead consider just passing poll.title and grabbing the selected option from state. Try something like this:
this.handleSubmit(title) {
// this.state.value should already have the selected option!
let obj = {
title,
option: this.state.value
};
// dispatch the object to redux, update your reducer, etc.
}
And in your render, be sure to bind poll.title as the argument:
render() {
...
<form onSubmit={this.handleSubmit.bind(this, poll.title)}>
}
Does that help at all? Let me know if I'm totally missing the mark on what you intend. With .bind() you pass the this context to use followed by a list of common separated args, so you could pass multiple args, but it's much easier to just grab option from state in this case.
Edit
If you want to access the SyntheticEvent that gets fired on submit, you simple specify it as the second argument to this.handleSubmit like so:
this.handleSubmit(title, event) {
// prevent form submit
event.preventDefault();
}
// this is the exact same as above, no need to pass event
render() {
...
<form onSubmit={this.handleSubmit.bind(this, poll.title)}>
}
In React, synthetic events are always passed as the last argument to a bound function and simply need to be specified to be in the method definition (no need to specify in render). This is Function.prototype.bind way of working with functions and events in React. Here are the supporting docs: https://reactjs.org/docs/handling-events.html#passing-arguments-to-event-handlers

Why is the title not showing on ion-nav-bar?

I have an ion-nav-bar that is hid during login/signin process. After signing in I have tabs on the bottom with the nav bar at the top. The ion-nav-bar can be seen but the title isn't showing anything. What am I doing wrong? Any help would be appreciated.
index.html
<body ng-app="mychat">
<!--
The nav bar that will be updated as we navigate between views.
-->
<ion-nav-bar class="bar-header bar-assertive">
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<!--
The views will be rendered in the <ion-nav-view> directive below
Templates are in the /templates folder (but you could also
have templates inline in this html file if you'd like).
-->
<ion-nav-view></ion-nav-view>
</body>
app.js
.config(function($stateProvider, $urlRouterProvider) {
console.log("setting config");
// Ionic uses AngularUI Router which uses the concept of states
// Learn more here: https://github.com/angular-ui/ui-router
// Set up the various states which the app can be in.
// Each state's controller can be found in controllers.js
$stateProvider
// State to represent Login View
.state('login', {
url: "/login",
templateUrl: "templates/login.html",
controller: 'LoginCtrl',
resolve: {
// controller will not be loaded until $waitForAuth resolves
// Auth refers to our $firebaseAuth wrapper in the example above
"currentAuth": ["Auth",
function (Auth) {
// $waitForAuth returns a promise so the resolve waits for it to complete
return Auth.$waitForAuth();
}]
}
})
// setup an abstract state for the tabs directive
.state('tab', {
url: "/tab",
abstract: true,
templateUrl: "templates/tabs.html",
resolve: {
// controller will not be loaded until $requireAuth resolves
// Auth refers to our $firebaseAuth wrapper in the example above
"currentAuth": ["Auth",
function (Auth) {
// $requireAuth returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError (see above)
return Auth.$requireAuth();
}]
}
})
// Each tab has its own nav history stack:
.state('tab.home', {
url: '/home',
views: {
'tab-home': {
templateUrl: 'templates/tab-home.html',
controller: 'HomeCtrl'
}
}
})
.state('tab.movies', {
url: '/movies',
views: {
'tab-movies': {
templateUrl: 'templates/tab-movies.html',
controller: 'MoviesCtrl'
}
}
})
.state('tab.people', {
url: '/people',
views: {
'tab-people': {
templateUrl: 'templates/tab-people.html',
controller: 'PeopleCtrl'
}
}
})
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/login');
});
tab-home.html
<ion-view title="Home">
<ion-content class="padding">
</ion-content>
</ion-view>
Use "view-title" attribute in tab-home.html
<ion-view view-title="my custom title" >
The most probable cause is the path of your templates not being correct. I have created this JSBin and it works fine with minor tweaks to the path of the login.html template. https://jsbin.com/loluva/1/edit?html,js,console,output
<script id="login.html" type='text/ng-template'>
<ion-view title="Login">
<ion-content class="padding">
</ion-content>
</ion-view>
</script>