Why is the vuex component isn't updated, despite the state updating? - components

When I load my page, I fetch a list of optboxes items.
Sources
Project's sources are online:
optboxes page ;
store (store, actions, mutations, getters).
optboxes pages
The HTTP request is well send and return adequate data:
created(){
this.getOptboxes();
},
components: {
'optbox': OptboxComponent,
},
methods: {
getOptboxes() {
optboxes.all().then((response) => {
this.setOptboxes(response.data.output);
}).catch(() = > {
this.no_optbox_message = 'there is no optbox';
logging.error(this.$t('optboxes.get.failed'))
});
}
},
vuex: {
actions: { setOptboxes: actions.setOptboxes},
getters: { optboxesList: getters.retrieveOptboxes}
}
I'm iterating over the results as follow:
<div v-for="optbox in optboxesList" class="panel panel-default">
<optbox :optbox="optbox"></optbox>
</div>
Store
const state = {
optboxes: {
/*
'akema': {
hostname: "192.168.2.23",
id: "akema",
printers: [
{
description: "bureau",
destination_port: 9100,
forward: "normal",
hostname: "1.2.3.4",
id: 0,
listening_port: 9102
}
]
}
*/
}
};
Question
If I switch to another pages and come back then the list appear. I also notice that with the Vuex extension I can commit the state and see the changes.
Why are my changes not applied automatically?

I had to change my data structure due to Change Detection Caveats.
Due to limitations of JavaScript, Vue.js cannot detect the following
changes to an Array:
When you directly set an item with the index, e.g. vm.items[0] = {};
When you modify the length of the Array, e.g. vm.items.length = 0.
Store
optboxes is now an array.
const state = {
optboxes:[]
}
Then update my mutations accordingly to edit the array.

Maybe it's an issue of reactivity?! I assume your setOptboxes mutation is not being picked up by vue:
setOptboxes(state, optboxes) {
for (var optbox of optboxes) {
state.optboxes[optbox.id] = optbox;
}
}
You can read about it here:
https://vuejs.org/guide/list.html#Caveats
https://vuejs.org/guide/reactivity.html
The docs solution is to use:
state.optboxes.$set(optbox.id, optbox);
which will trigger a view update.

Using Vue.set dit it for me. Take a look here

Related

Eslint rule is running multiple times

I'm trying to write an eslint rule that enforces making sure the name property is defined on any classes that extend from other Error/Exception named classes (and fixes them).
As far as I can tell, it works in the astexplorer.net individually, but when I'm running it alongside other rules, it ends up getting ran multiple times, so the name property ends up being repeated multiple times in the resulting "fixed" file.
Is there anything in particular I can do to prevent it being run multiple times? I'm assuming what's happening is that it's inserting my name = 'ClassName';, then prettier is needing to reformat the code, which it does, but then maybe it's re-running my rule? I'm not sure.
Rule/fix code shown below. I've tried things like using *fix and yield, but that doesn't seem to help either (see commented code below, based on information in the eslint documentation)
module.exports = {
meta: {
hasSuggestions: true,
type: 'suggestion',
docs: {},
fixable: 'code',
schema: [], // no options,
},
create: function (context) {
return {
ClassDeclaration: function (node) {
const regex = /.*(Error|Exception)$/;
// If the parent/superClass is has "Error" or "Exception" in the name
if (node.superClass && regex.test(node.superClass.name)) {
let name = null;
const className = node.id.name;
// Test class object name
if (!regex.test(className)) {
context.report({
node: node,
message: 'Error extensions must end with "Error" or "Exception".',
});
}
// Find name ClassProperty
node.body.body.some(function (a) {
if (a.type === 'ClassProperty' && a.key.name === 'name') {
name = a.value.value;
return true;
}
});
// Name property is required
if (!name) {
context.report({
node: node,
message: 'Error extensions should have a descriptive name',
fix(fixer) {
return fixer.replaceTextRange(
[node.body.range[0]+1, node.body.range[0]+1],
`name = '${className}';`
);
},
// *fix(fixer) {
// name = className;
// yield fixer.replaceTextRange(
// [node.body.range[0]+1, node.body.range[0]+1],
// `name = '${className}';`
// );
//
// // extend range of the fix to the range of `node.parent`
// yield fixer.insertTextBefore(node.body, '');
// yield fixer.insertTextAfter(node.body, '');
// },
});
}
}
},
};
},
};
Turns out I had the AST Explorer set to the wrong parser, so it was showing me the wrong string name for the ClassProperty node. I should have been using PropertyDefinition instead.

How to prevent malicious Meteor subscriptions using check

I found an important security fault in my meteor app regarding subscriptions (maybe methods are also affected by this).
Even though I use the check package and check() assuring that the correct parameters data types are received inside the publication, I have realised that if a user maliciously subscribes to that subscription with wrong parameter data types it is affecting all other users that are using the same subscription because the meteor server is not running the publication while the malicious user is using incorrect parameters.
How can I prevent this?
Packages used:
aldeed:collection2-core#2.0.1
audit-argument-checks#1.0.7
mdg:validated-method
and npm
import { check, Match } from 'meteor/check';
Server side:
Meteor.publish('postersPub', function postersPub(params) {
check(params, {
size: String,
section: String,
});
return Posters.find({
section: params.section,
size: params.size === 'large' ? 'large' : 'small',
}, {
// fields: { ... }
// sort: { ... }
});
});
Client side:
// in the template:
Meteor.subscribe('postersPub', { size: 'large', section: 'movies' });
// Malicious user in the browser console:
Meteor.subscribe('postersPub', { size: undefined, section: '' });
Problem: The malicious user subscription is preventing all other users of getting answer from their postersPub subscriptions.
Extra note: I've also tried wrapping the check block AND the whole publication with a try catch and it doesn't change the effect. The error disappears from the server console, but the other users keep being affected and not getting data from the subscription that the malicious user is affecting.
Check method and empty strings
There is one thing to know about check and strings which is, that it accepts empty strings like '' which you basically showed in your malicious example.
Without guarantee to solve your publication issue I can at least suggest you to modify your check code and include a check for non-empty Strings.
A possible approach could be:
import { check, Match } from 'meteor/check';
const nonEmptyString = Match.Where(str => typeof str === 'string' && str.length > 0);
which then can be used in check like so:
check(params, {
size: nonEmptyString,
section: nonEmptyString,
});
Even more strict checks
You may be even stricter with accepted parameters and reduce them to a subset of valid entries. For example:
const sizes = ['large', 'small'];
const nonEmptyString = str => typeof str === 'string' && str.length > 0;
const validSize = str => nonEmptyString(str) && sizes.indexOf( str) > -1;
check(params, {
size: Match.Where(validSize),
section: Match.Where(nonEmptyString),
});
Note, that this also helps you to avoid query logic based on the parameter. You can change the following code
const posters = Posters.find({
section: params.section,
size: params.size === 'large' ? 'large' : 'small',
}, {
// fields: { ... }
// sort: { ... }
});
to
const posters = Posters.find({
section: params.section,
size: params.size,
}, {
// fields: { ... }
// sort: { ... }
});
because the method does anyway accept only one of large or small as parameters.
Fallback on undefined cursors in publications
Another pattern that can support you preventing publication errors is to call this.ready() if the collection returned no cursor (for whatever reason, better is to write good tests to prevent you from these cases).
const posters = Posters.find({
section: params.section,
size: params.size === 'large' ? 'large' : 'small',
}, {
// fields: { ... }
// sort: { ... }
});
// if we have a cursor with count
if (posters && posters.count && posters.count() >= 0)
return posters;
// else signal the subscription
// that we are ready
this.ready();
Combined code example
Applying all of the above mentioned pattern would make your function look like this:
import { check, Match } from 'meteor/check';
const sizes = ['large', 'small'];
const nonEmptyString = str => typeof str === 'string' && str.length > 0;
const validSize = str => nonEmptyString(str) && sizes.indexOf( str) > -1;
Meteor.publish('postersPub', function postersPub(params) {
check(params, {
size: Match.Where(validSize),
section: Match.Where(nonEmptyString),
});
const posters = Posters.find({
section: params.section,
size: params.size,
}, {
// fields: { ... }
// sort: { ... }
});
// if we have a cursor with count
if (posters && posters.count && posters.count() >= 0)
return posters;
// else signal the subscription
// that we are ready
this.ready();
});
Summary
I for myself found that with good check matches and this.ready() the problems with publications have been reduced to a minimum in my applications.

Getting image URL from Contentful entry id

I need to get an image URL from Contentful entry id.
I am getting such an JSON from Contentful query
{
"sys":{
"space":{
"sys":{
"type":"Link",
"linkType":"Space",
"id":"8v1e7eaw70p2"
}
},
"id":"1JfEwVlD9WmYikE8kS8iCA",
"type":"Entry",
"createdAt":"2018-02-28T18:50:08.758Z",
"updatedAt":"2018-02-28T18:50:08.758Z",
"revision":1,
"contentType":{
"sys":{
"type":"Link",
"linkType":"ContentType",
"id":"image"
}
},
"locale":"en-US"
},
"fields":{
"name":"heat",
"image":{
"sys":{
"type":"Link",
"linkType":"Asset",
"id":"6Inruq2U0M2kOYsSAu8Ywk"
}
}
}
}
I am using JS driver they provide:
client.getEntry()
so how to go thru that link: 6Inruq2U0M2kOYsSAu8Ywk ?
Unfortunately, the js SDK will not be able to resolve links when using the single entry endpoint i.e client.getEntry() because there won't be enough data.
When thing I always recommend to work around this is to use the collection endpoint with a query the desired id as a query param. This way you will always get the desired entry with all it's linked data.
Your code should look something like this
client.getEntries({'sys.id': '6Inruq2U0M2kOYsSAu8Ywk'})
.then(response => console.log(response.items[0].fields.image.fields.file.url))
I hope that helps.
Best,
Khaled
Use client.getEntries({'sys.id': '1JfEwVlD9WmYikE8kS8iCA'})
To get the entry fields and the asset fields.
You can also patch the assets to the fields by running this after fetching the data:
/* Patch all the assets to the fields */
const patchAssets = (fields, assets) => {
Object.keys(fields).forEach(function (key) {
let obj = fields[key];
if (obj.sys && obj.sys.linkType === 'Asset') {
const assetId = obj.sys.id;
const matchAsset = assets.find(asset => {
return asset.id === assetId;
});
obj.file = matchAsset;
}
});
return fields;
};
Another way to get image url is to use getAsset('<asset_id>'). So first, using the getEntry() method, you need to get the entry data, then extract the id from the field: fields.image.sys.id, and pass it to the getAsset method.

Migrating to FlowRouter, need something similar to a template data context

So I've read a lot on the discussion of Iron Router vs FlowRouter.
I started my project using Iron Router, but since changed my mind and I'm currently migrating to FlowRouter.
Everything was going smoothly until I started migrating the comments section of my app. You see, this section is reused several times on the app, it serves as a comment section for news, posts, photos, videos, etc.
Example using IR's data context:
Router.route('/news/:slug', {
name: 'newsItem',
waitOn: function() { Meteor.subscribe('news.single', this.params.slug) },
data: function() {
return News.findOne({slug: this.params.slug});
}
});
<template name="newsItem">
<p>{{title}}</p>
<p>{{body}}</p>
{{> commentSection}}
</template>
The Comment collection schema has a "type" (to know to what type of "thing" this comment belongs to, news, photos, etc). That type was set on the "form .submit" event of commentSection template. Example:
'submit form': function(e, template) {
e.preventDefault();
var $body = $(e.target).find('[name=body]');
console.log(template.data.type);
var comment = {
type: template.data.type,
parentId: template.data._id,
parentSlug: template.data.slug,
body: $body.val()
};
Meteor.call('insertComment', comment, function(error, commentId) {
if (error){
alert(error.reason);
} else {
$body.val('');
}
});
}
This worked because the template data context contained the News item which in turn has a a type property as well.
How could I achieve something similar to this only using Flow Router without setting data on the template as it is recommended by the official guide?
You'll want to use a template subscription and a {{#with}} helper probably.
Template.newsItem.onCreated( function() {
Template.instance().subscribe('news.single', FlowRouter.current().params.slug);
});
Template.newsItem.helpers({
item() {
let item = News.findOne();
if( item ) {
return item;
}
}
});
<template name="newsItem">
{{#with item}}
<!-- Your existing stuff -->
{{/with}}
</template>

How to filter urls with # (hash tag) using UrlFilter in a chrome.declarativeContent.PageStateMatcher

I've just started building a chrome extension and as I need to display its icon only for specific urls, I used page_action.
I also used an event listening if the url changes and matches my pattern that way to display the icon:
chrome.declarativeContent.onPageChanged.addRules([
{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlContains: 'https://mysite.com/mypage.html' }
})
],
actions: [ new chrome.declarativeContent.ShowPageAction() ]
}
]);
It works fine but when I want to add a filter of the first character of the query, it fails.
The url pattern I want to filter looks like:
https://mysite.com/mypage.html#e123456789
I tried the following but it didn't help:
pageUrl: { urlContains: 'https://mysite.com/mypage.html#e' }
pageUrl: { urlContains: 'https://mysite.com/mypage.html', queryPrefix: '#e' }
pageUrl: { urlContains: 'https://mysite.com/mypage.html', queryPrefix: 'e' }
I think that the issue comes from the hash tag.
Any idea of a workaround ?
The #... part of a URL is called a "reference fragment" (ocassionally referred to as "hash").
Reference fragments are currently not supported in URLFilters, there is already a bug report for this feature: Issue 84024: targetUrlPatterns and URL search/hash component.
If you really want to show the page action depending on the state of the reference fragment, then you could use the chrome.webNavigation.onReferenceFragmentUpdated event instead of the declarativeContent API. For example (adapted from my answer to How to show Chrome Extension on certain domains?; see that answer for the manifest.json to use for testing):
function onWebNav(details) {
var refIndex = details.url.indexOf('#');
var ref = refIndex >= 0 ? details.url.slice(refIndex+1) : '';
if (ref.indexOf('e') == 0) { // Starts with e? show page action
chrome.pageAction.show(details.tabId);
} else {
chrome.pageAction.hide(details.tabId);
}
}
// Base filter
var filter = {
url: [{
hostEquals: 'example.com'
}]
};
chrome.webNavigation.onCommitted.addListener(onWebNav, filter);
chrome.webNavigation.onHistoryStateUpdated.addListener(onWebNav, filter);
chrome.webNavigation.onReferenceFragmentUpdated.addListener(onWebNav, filter);

Resources