How do I query a Meteor.publication collection by a component prop correctly? - mongodb

Given the following React component, I am passing this component this.ticket.props AND I am subscribing to a collection called userReservations. The ticket properties are rendering correctly i.e. each ticket is rendering only it's specific properties.
However inside the .renderReservationInfo() method. After I make the Meteor.subscription call with the ticket._id value as a selector in the createContainer class, which corresponds to the "ticketId" value in the UserReservation collection, the Meteor publication responds with the correct collection info but renders it to every ticket.
If I'm querying by ticket._id, why is the collection applied to both tickets? How can I render just the reservation info to the corresponding ticket._id?
Are there react lifecycle components that can help me manage this?
class ComponentName extends React.Component {
renderReservationInfo() {
let {
ticket,
UserReservation
} = this.props;
return UserReservation.map((reservation) => {
return (
<div key = {reservation._id} >
< h4 > {reservation.property1} < /h4>
< h4 > {reservation.property2} < /h4>
< h4 > {reservation.property3} < /h4>
<Component2 prop = {reservation} />
</div >
)
});
}
render() {
return (
<div >
< div className = "make-it-pretty" >
< h4 > Ticket Price: $ { this.props.ticket.property1 } </h4>
< h4 > Ticket Price: $ { this.props.ticket.property2 } </h4>
< h4 > Ticket Price: $ { this.props.ticket.property3 } </h4>
{ this.renderReservationInfo() }
</div>
</div >
)
}
}
export default createContainer(({ ticket }) => {
Meteor.subscribe('userReservations', ticket._id);
return {
UserReservation: UserReservations.find({}).fetch()
};
}, ComponentName);
And my Publication info
//Server
Meteor.publish('userReservations', function(ticket_Id) {
if (!ticket_Id) {
console.log('No ticketId sent');
} else {
console.log('sending UserReservations', ticket_Id);
return UserReservations.find({ ticketId:ticket_Id });
}
});

Okay so this turned out to be a simple enough solution. I had previously thought I needed to keep my subscribe call empty in the client side for security purposes, and maybe that's an accurate statement, but inserting the the specific ticketId I was able to start returning what I needed to in the correct way.
Also by making stricter query parameters in my publication.find() I was able to limit what was sent back. I think this is probably an example of a simple problem that I snowballed into a bigger one.

Related

List.JS - Filter with comma separated values

I'm trying to create a dropdown to filter based on dates, but I would like to be able to comma separate the dates instead of initilizing each field as a separate filter, which would be slow when you account for hundreds of dates, and each list item having 30+ dates each.
I thought maybe list.js supports comma separated fields, but I can't find any obvious solution online.
Here is a simple codepen with the code working with a single field:
https://codepen.io/mauricekindermann/pen/QWyqzQL
This: <span class="stat filter_dates">1999,2099,2199</span>
Instead of this: <span class="stat filter_dates">1999</span>
Is this possible? Or do I need to initiate each date as a separate filter?
I didn't get any answers and couldn't find an obvious soultion to this. So the final result uses this method (JS script inside a PHP file)
<script>
var options = {
valueNames: [
'id',
<?
while($item= mysqli_fetch_array($query))
{
?>
'filter_<?=$item['id']?>',
<?
}
?>
]
};
$(document).ready(function()
{
if($('#filter').length > 0)
{
$('#filter').change(function ()
{
var selection = this.value;
<?
$i=0;
mysqli_data_seek($query, 0);
while($item= mysqli_fetch_array($query))
{
if($i==0){
$type='if';
} else {
$type='else if';
}
?>
<?=$type?>(selection == '<?=$item['id']?>')
{
userList.filter(function (item) {
return (item.values().<?='filter_'.$item['id']?> == selection);
});
}
<?
$i++;
}
if($i > 0){
?>
else {
userList.filter();
}
<?
}
?>
});
};
});
</script>

Input type number "only numeric value" validation

How can I validate an input of type="number" to only be valid if the value is numeric or null using only Reactive Forms (no directives)?
Only numbers [0-9] and . are allowed, no "e" or any other characters.
What I've tried so far:
Template:
<form [formGroup]="form" novalidate>
<input type="number" formControlName="number" id="number">
</form>
Component:
export class App {
form: FormGroup = new FormGroup({});
constructor(
private fb: FormBuilder,
) {
this.form = fb.group({
number: ['', [CustomValidator.numeric]]
})
}
}
CustomValidator:
export class CustomValidator{
// Number only validation
static numeric(control: AbstractControl) {
let val = control.value;
if (val === null || val === '') return null;
if (!val.toString().match(/^[0-9]+(\.?[0-9]+)?$/)) return { 'invalidNumber': true };
return null;
}
}
Plunker
The problem is when a user enters something that is not a number ("123e" or "abc") the FormControl's value becomes null, keep in mind I don't want the field to be required so if the field really is empty null value should be valid.
Cross browser support is also important (Chrome's number input fields do not allow the user to input letters - except "e", but FireFox and Safari do).
In the HTML file, you can add ngIf for your pattern like this,
<div class="form-control-feedback" *ngIf="Mobile.errors && (Mobile.dirty || Mobile.touched)">
<p *ngIf="Mobile.errors.pattern" class="text-danger">Number Only</p>
</div>
In .ts file you can add the Validators pattern -"^[0-9]*$"
this.Mobile = new FormControl('', [
Validators.required,
Validators.pattern("^[0-9]*$"),
Validators.minLength(8),
]);
Using directive it becomes easy and can be used throughout the application
HTML
<input type="text" placeholder="Enter value" numbersOnly>
As .keyCode() and .which() are deprecated, codes are checked using .key()
Referred from
Directive:
#Directive({
selector: "[numbersOnly]"
})
export class NumbersOnlyDirective {
#Input() numbersOnly:boolean;
navigationKeys: Array<string> = ['Backspace']; //Add keys as per requirement
constructor(private _el: ElementRef) { }
#HostListener('keydown', ['$event']) onKeyDown(e: KeyboardEvent) {
if (
// Allow: Delete, Backspace, Tab, Escape, Enter, etc
this.navigationKeys.indexOf(e.key) > -1 ||
(e.key === 'a' && e.ctrlKey === true) || // Allow: Ctrl+A
(e.key === 'c' && e.ctrlKey === true) || // Allow: Ctrl+C
(e.key === 'v' && e.ctrlKey === true) || // Allow: Ctrl+V
(e.key === 'x' && e.ctrlKey === true) || // Allow: Ctrl+X
(e.key === 'a' && e.metaKey === true) || // Cmd+A (Mac)
(e.key === 'c' && e.metaKey === true) || // Cmd+C (Mac)
(e.key === 'v' && e.metaKey === true) || // Cmd+V (Mac)
(e.key === 'x' && e.metaKey === true) // Cmd+X (Mac)
) {
return; // let it happen, don't do anything
}
// Ensure that it is a number and stop the keypress
if (e.key === ' ' || isNaN(Number(e.key))) {
e.preventDefault();
}
}
}
Simplest and most effective way to do number validation is (it will restrict space and special character also)
if you dont want length restriction you can remove maxlength property
HTML
<input type="text" maxlength="3" (keypress)="validateNo($event)"/>
TS
validateNo(e): boolean {
const charCode = e.which ? e.which : e.keyCode;
if (charCode > 31 && (charCode < 48 || charCode > 57)) {
return false
}
return true
}
I had a similar problem, too: I wanted numbers and null on an input field that is not required. Worked through a number of different variations. I finally settled on this one, which seems to do the trick. You place a Directive, ntvFormValidity, on any form control that has native invalidity and that doesn't swizzle that invalid state into ng-invalid.
Sample use:
<input type="number" formControlName="num" placeholder="0" ntvFormValidity>
Directive definition:
import { Directive, Host, Self, ElementRef, AfterViewInit } from '#angular/core';
import { FormControlName, FormControl, Validators } from '#angular/forms';
#Directive({
selector: '[ntvFormValidity]'
})
export class NtvFormControlValidityDirective implements AfterViewInit {
constructor(#Host() private cn: FormControlName, #Host() private el: ElementRef) { }
/*
- Angular doesn't fire "change" events for invalid <input type="number">
- We have to check the DOM object for browser native invalid state
- Add custom validator that checks native invalidity
*/
ngAfterViewInit() {
var control: FormControl = this.cn.control;
// Bridge native invalid to ng-invalid via Validators
const ntvValidator = () => !this.el.nativeElement.validity.valid ? { error: "invalid" } : null;
const v_fn = control.validator;
control.setValidators(v_fn ? Validators.compose([v_fn, ntvValidator]) : ntvValidator);
setTimeout(()=>control.updateValueAndValidity(), 0);
}
}
The challenge was to get the ElementRef from the FormControl so that I could examine it. I know there's #ViewChild, but I didn't want to have to annotate each numeric input field with an ID and pass it to something else. So, I built a Directive which can ask for the ElementRef.
On Safari, for the HTML example above, Angular marks the form control invalid on inputs like "abc".
I think if I were to do this over, I'd probably build my own CVA for numeric input fields as that would provide even more control and make for a simple html.
Something like this:
<my-input-number formControlName="num" placeholder="0">
PS: If there's a better way to grab the FormControl for the directive, I'm guessing with Dependency Injection and providers on the declaration, please let me know so I can update my Directive (and this answer).
IMO the most robust and general way to do this is by checking if the value may be converted to number. For that add a validator:
numberValidator(control: FormControl) {
if (isNaN(control?.value)) {
return {
number: true
}
}
return null;
}
export class App {
form: FormGroup = new FormGroup({});
constructor(
private fb: FormBuilder,
) {
this.form = fb.group({
number: ['', [numberValidator]]
})
}
}
You can combine it with Validators.min and/or Validators.max to further limiting the accepted values.
The easiest way would be to use a library like this one and specifically you want noStrings to be true
export class CustomValidator{ // Number only validation
static numeric(control: AbstractControl) {
let val = control.value;
const hasError = validate({val: val}, {val: {numericality: {noStrings: true}}});
if (hasError) return null;
return val;
}
}
Try to put a minimum input and allow only numbers from 0 to 9. This worked for me in Angular Cli
<input type="number" oninput="this.value=this.value.replace(/[^\d]/,'')" min=0>
You need to use regular expressions in your custom validator. For example, here's the code that allows only 9 digits in the input fields:
function ssnValidator(control: FormControl): {[key: string]: any} {
const value: string = control.value || '';
const valid = value.match(/^\d{9}$/);
return valid ? null : {ssn: true};
}
Take a look at a sample app here:
https://github.com/Farata/angular2typescript/tree/master/Angular4/form-samples/src/app/reactive-validator
Sometimes it is just easier to try something simple like this.
validateNumber(control: FormControl): { [s: string]: boolean } {
//revised to reflect null as an acceptable value
if (control.value === null) return null;
// check to see if the control value is no a number
if (isNaN(control.value)) {
return { 'NaN': true };
}
return null;
}
Hope this helps.
updated as per comment,
You need to to call the validator like this
number: new FormControl('',[this.validateNumber.bind(this)])
The bind(this) is necessary if you are putting the validator in the component which is how I do it.

How to prevent individual form elements from updating using Meteor + React?

I have a Meteor + React single-page-application with a basic form in it. Data is collected from MongoDB using the createContainer method and passed to a form component. The problem I am facing is this. A user starts completing the form but, if the data that originally populated the form changes (by another user somewhere else in the world saving the form), the createContainer method will re-compute, which in turn pushes a new set of props to the form component and therefore overwrites what the user is typing in.
For many reasons, I cannot use the shouldComponentUpdate lifecycle method within the form component. One reason is that the form contains a select element, whose list of items should still accept reactive updates.
I need a way of halting certain reactive updates, but allowing others, whilst the user is completing the form. Any suggestions?
export default FormContainer = createContainer(( params ) => {
const dataFormHandle = Meteor.subscribe('FormsPub');
const dataFormIsReady = dataFormHandle.ready();
const dataListHandle = Meteor.subscribe('ListItemsPub');
const dataListIsReady = dataListHandle.ready();
let name = "";
let listItems = [];
let listSelectedValue = null;
if(dataListIsReady) {
listItems = collections.ListItemsColl.find({_id: ListId}).fetch();
}
if(dataFormIsReady) {
let formData = collections.FormsColl.find({_id: formId}).fetch();
name = formData[0].name;
listSelectedValue = formData[0].listSelectedValue;
}
return {
name,
listItems,
listSelectedValue
};
}, Form);
...
export default class Form extends Component {
constructor(props) {
super(props);
this.state = {
name: (this.props.name) ? this.props.name : "",
listSelectedValue: (this.props.listSelectedValue) ? this.props.listSelectedValue : null
};
}
componentWillReceiveProps(nextProps) {
this.setState({name: (nextProps.name) ? nextProps.name : ""});
this.setState({listSelectedValue: (nextProps.listSelectedValue) ? nextProps.listSelectedValue : null});
}
updateFormState(){
var name = e.target.name;
var val = e.target.value;
if(name == "name"){this.setState({name: val});}
if(name == "list"){
if( typeof e.target[e.target.selectedIndex] != "undefined" ) {
this.setState({listSelectedValue: val});
}
}
}
render(){
return (
<div>
<input type="text" name="name" value={this.state.name} onChange={this.updateFormState.bind(this)} />
<Select2
value={this.state.listSelectedValue}
name="list"
onChange={this.updateFormState.bind(this)}
options={{
minimumResultsForSearch: Infinity
}}
data={this.props.listItems}
/>
</div>
);
}
}
For the data in the form that you wish to be non-reactive simply specify reactive: false in your .find(), for example:
let formData = collections.FormsColl.find({ _id: formId },{ reactive: false }).fetch();
This will prevent the data from reactively updating while the form is open.

Trying to work with down() method from ExtJS 4.2.1

I am trying to find a specific element from my page using ExtJS 4 so I can do modifications on it.
I know its id so it should not be a problem BUT
-I tried Ext.getCmp('theId') and it just return me undefined
-I tried to use down('theId') method by passing through the view and I still get a nullresponse.
As I know the id of the element I tried again the two methods by setting manually the id and it didn't work neither.
Do these two methods not function?
How should I do?
Here is the concerned part of the code :
listeners: {
load: function(node, records, successful, eOpts) {
var ownertree = records.store.ownerTree;
var boundView = ownertree.dockedItems.items[1].view.id;
var generalId = boundView+'-record-';
// Add row stripping on leaf nodes when a node is expanded
//
// Adding the same feature to the whole tree instead of leaf nodes
// would not be much more complicated but it would require iterating
// the whole tree starting with the root node to build a list of
// all visible nodes. The same function would need to be called
// on expand, collapse, append, insert, remove and load events.
if (!node.tree.root.data.leaf) {
// Process each child node
node.tree.root.cascadeBy(function(currentChild) {
// Process only leaf
if (currentChild.data.leaf) {
var nodeId = ""+generalId+currentChild.internalId;
var index = currentChild.data.index;
if ((index % 2) == 0) {
// even node
currentChild.data.cls.replace('tree-odd-node', '')
currentChild.data.cls = 'tree-even-node';
} else {
// odd node
currentChild.data.cls.replace('tree-even-node', '')
currentChild.data.cls = 'tree-odd-node';
}
// Update CSS classes
currentChild.triggerUIUpdate();
console.log(nodeId);
console.log(ownertree.view.body);
console.log(Ext.getCmp(nodeId));
console.log(Ext.getCmp('treeview-1016-record-02001001'));
console.log(ownertree.view.body.down(nodeId));
console.log(ownertree.view.body.down('treeview-1016-record-02001001'));
}
});
}
}
You can see my console.log at the end.
Here is what they give me on the javascript console (in the right order):
treeview-1016-record-02001001
The precise id I am looking for. And I also try manually in case...
h {dom: table#treeview-1016-table.x-treeview-1016-table x-grid-table, el: h, id: "treeview-1016gridBody", $cache: Object, lastBox: Object…}
I checked every configs of this item and its dom and it is exactly the part of the dom I am looking for, which is the view containing my tree. The BIG parent
And then:
undefined
undefined
null
null
Here is the item I want to access:
<tr role="row" id="treeview-1016-record-02001001" ...>
And I checked there is no id duplication anywhere...
I asked someone else who told me these methods do not work. The problem is I need to access this item to modify its cls.
I would appreciate any idea.
You are looking for Ext.get(id). Ext.getCmp(id) is used for Ext.Components, and Ext.get(id) is used for Ext.dom.Elements. See the docs here: http://docs.sencha.com/extjs/4.2.1/#!/api/Ext-method-get
Ok so finally I used the afteritemexpand listener. With the ids I get the elements I am looking for with your Ext.get(id) method kevhender :).
The reason is that the dom elements where not completely loaded when I used my load listener (it was just the store) so the Ext.get(id) method couldn't get the the element correctly. I first used afterlayout listener, that was correct but too often called and the access to the id was not so easy.
So, here is how I did finally :
listeners: {
load: function(node, records, successful, eOpts) {
var ownertree = records.store.ownerTree;
var boundView = ownertree.dockedItems.items[1].view.id;
var generalId = boundView+'-record-';
if (!node.tree.root.data.leaf) {
// Process each child node
node.tree.root.cascadeBy(function(currentChild) {
// Process only leaf
if (currentChild.data.leaf) {
var nodeId = ""+generalId+currentChild.internalId;
var index = currentChild.data.index;
if ( (index % 2) == 0 && ids.indexOf(nodeId) == -1 ) {
ids[indiceIds] = nodeId;
indiceIds++;
}
console.log(ids);
}
});
}
},
afteritemexpand: function( node, index, item, eOpts ){
/* This commented section below could replace the load but the load is done during store loading while afteritemexpand is done after expanding an item.
So, load listener makes saving time AND makes loading time constant. That is not the case if we just consider the commented section below because
the more you expand nodes, the more items it will have to get and so loading time is more and more important
*/
// var domLeaf = Ext.get(item.id).next();
// for ( var int = 0; int < node.childNodes.length; int++) {
// if (node.childNodes[int].data.leaf && (int % 2) == 0) {
// if (ids.indexOf(domLeaf.id) == -1) {
// ids[indiceIds] = domLeaf.id;
// indiceIds++;
// }
// }
// domLeaf = domLeaf.next();
// }
for ( var int = 0; int < ids.length; int++) {
domLeaf = Ext.get(ids[int]);
if (domLeaf != null) {
for ( var int2 = 0; int2 < domLeaf.dom.children.length; int2++) {
if (domLeaf.dom.children[int2].className.search('tree-even-node') == -1){
domLeaf.dom.children[int2].className += ' tree-even-node';
}
}
}
}
},
With ids an Array of the ids I need to set the class.
Thank you for the method.

Can I use the > operator in a KRL query() selector?

I want to get a nested DIV tag using KRL query() but it complains with
ERROR Rules.pm a8x40 show_xfers Ruleset a8x40 failed: html.query error - Invalid specification ">div" in query: div.recent-transfer>div
Here's the HTML fragment (there are multiple in the file):
<div class='recent-transfer'>
<span>...</span>
<div> <!-- * * * -->
<div>...</div>
<div>...</div>
</div>
</div>
Here's my function:
recent = function() {
t = http:get(the_url).pick("$..content");
t.query("div.recent-transfer>div")
}
I want to select the DIV marked * * *. Do I need to chain several query() statements to get the DIV?
When I tried to reproduce your problem, I didn't get the same error. Instead, I would get a "NOT_FOUND_ERR: DOM Exception 8". In my case, it wasn't a problem with the selector at all; it was the fact that the return value of t.query was an array. If I wanted to use that in, say, a notify(), I had to get the 0th element out of the array and return that instead.
I don't know if that is the same problem you are having. But here's a sample ruleset that works for me:
ruleset a163x61 {
meta {
name "Selector test"
description <<
Testing the query() function
>>
author "Steve Nay"
logging on
}
dispatch {
}
global {
the_url = "http://students.cs.byu.edu/~snay2/content.html";
recent = function() {
t = http:get(the_url).pick("$..content");
// This produces an array.
text = t.query("div.recent-transfer>div");
// We want the text out of the element. Get the first element.
text[0];
// This won't work; throws a "NOT_FOUND_ERR: DOM Exception 8"
//text;
};
}
rule first_rule {
select when pageview ".*" setting ()
pre {
disp = recent();
}
notify("Content:", disp) with sticky=true;
}
}
"div.recent-transfer>div" is a valid query. There was a problem in the KNS causing intermittent failures.
Here's how the function is used, such that the returned array doesn't make problems:
rule add_content {
select when pageview ".*"
foreach recent() setting (item) {
append("div#main", item);
}
}