Nuxt / Vue 2 override input value after change - html

I'm trying to create an input that converts the user's pressed keys to the last single upper-cased character (if the user types 'a' -> 'A', 'abc' -> 'C').
This is my code:
<template>
<b-form-input
ref="firstNameInitial"
v-model="firstNameInitial"
/>
</template>
<script>
export default {
firstNameInitial: {
get () {
return this.$store.state.certificateAdd.firstNameInitial
},
async set (val) {
const newValue = StringUtils.convertToNameInitial(val)
this.$store.commit('certificateAdd/setFirstNameInitial', newValue)
// Tried this as well
this.$refs.firstNameInitial.$el.value = newValue
}
}
}
</script>
This works fine if I type distinct letters, but if I type "aa" the input will show "Aa" since the getter isn't recomputed (vuex still holds the same data: "A").
Even if I trick getter by refencing some dummy local data that gets incremented in the setter the component will not re-render because the value prop is not changed; but the underlining HTML input's value will always update even if Vue doesn't step in to change it.
I've even tried manipulating the DOM directly but that doesn't work either.
Any ideas on how this behaviour could be implemented.

You're not supposed to use a mutation since it is synchronous. You should use a vuex action because of the way DOM will handle the updates. If you have it as an action, I think it may work fine.
If it does not work, maybe try both: await this.$nextTick() and then action (in this order).

Related

ember hasDirtyAttributes do not work with object (json)

I have a model:
export default Model.extend({
title: attr('string'),
attributes: attr('jsonb')
});
Where attributes is a custom json filed stored as jsonb in Postgres.
let say:
{
"name":"Bob",
"city":""
}
So I can easily manipulate attributes using template
<form.element .. #property="attributes.city"/> or model.set('attributes.city','city name')
Problem: hasDirtyAttributes do not changing because technically we have old object. But when I try to copy object let say
JSON.parse(JSON.stringify(this.get('attributes')) hasDirtyAttributes works as expected
So how to write some Mixin for a Model or other workaround which on the change of any attribute property will mark hasDirtyAttributes as true. I will update whole object so doesn't matter which property actually was changed.
Same problem: https://discuss.emberjs.com/t/hasdirtyattributes-do-not-work-with-nested-attributes-json-api/15592
existing solution doesn't work for me at all:
ember-dirtier
ember-data-relationship-tracker
ember-data-model-fragments (a lot of changes under the hood and broke my app)
Update:
Some not perfect idea that help better describe what I'm want to achieve:
Let say we adding observer to any object fileds:
export default Model.extend({
init: function(){
this._super();
this.set('_attributes', Object.assign({}, this.get('attributes'))); //copy original
Object.keys(this.get('attributes')).forEach((item) => {
this.addObserver('attributes.'+ item, this, this.objectObserver);
});
}
...
})
And observer:
objectObserver: function(model, filed){
let privateFiled = '_' + filed;
if (model.get(privateFiled) != model.get(filed)) { //compare with last state
model.set(privateFiled, this.get(filed));
model.set('attributes', Object.assign({}, this.get('attributes')) );
}
}
It's works, but when I change one filed in object due to copying object objectObserver faired again on every filed. So in this key changing every filed in object I mark observed filed as dirty
The further ember development will reduce using of event listener and two-way binding, actually Glimmer components supports only One-way Data Flow. So to be friendly with future versions of emberusing one-way data flow is good approach in this case to. So In my case as I use ember boostrap solution looks like
<form.element #controlType="textarea" #onChange={{action 'attributeChange'}}
where attributeChange action do all works.
New Glimmer / Octane style based on modifier and looks like:
{{!-- templates/components/child.hbs --}}
<button type="button" {{on "click" (fn #onClick 'Hello, moon!')}}>
Change value
</button>

How to get a list via POST in Restangular?

Consider a REST URL like /api/users/findByCriteria which receives POSTed JSON that contains details of the criteria, and outputs a list of Users.
How would one call this with Restangular so that its results are similar to Restangulars getList()?
Restangular.all('users').post("findByCriteria", crit)... might work, but I don't know how to have Restangular recognize that the result will be a list of Users
Restangular.all('users').getListFromPOST("findByCriteria", crit)... would be nice to be able to do, but it doesn't exist.
Doing a GET instead of a POST isn't an option, because the criteria is complex.
Well,
I experience same problem and I workaround it with plain function, which return a plain array of objects. but it will remove all Restangular helper functions. So, you cant use it.
Code snippet:
Restangular.one('client').post('list',JSON.stringify({
offset: offset,
length: length
})).then(
function(data) {
$scope.clients = data.plain();
},
function(data) {
//error handling
}
);
You can get a POST to return a properly restangularized collection by setting a custom handler for OnElemRestangularized in a config block. This handler is called after the object has been Restangularized. isCollection is passed in to show if the obect was treated as a collection or single element. In the code below, if the object is an array, but was not treated as collection, it is restangularized again, as a collection. This adds all the restangular handlers to each element in the array.
let onElemR = (changedElem, isCollection, route, Restangular: restangular.IService) => {
if (Array.isArray(changedElem) && !isCollection ) {
return Restangular.restangularizeCollection(null, changedElem, changedElem.route);
}
return changedElem;
};
RestangularProvider.setOnElemRestangularized(onElemR);

KnockoutJS dynamic form validation

I have a form that have a three field group that on a click of a "Add New" buttom other three field group will be added. That part is working great.
I want to add a validation so all three fields are required in order to add a new group.
For reference here is the code working: http://jsfiddle.net/5g8Xc/
var ContactsModel = function(contacts) {
var self = this;
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function(contact) {
return { firstName: contact.firstName, fathersLast: contact.fathersLast, country: contact.country };
}));
self.addContact = function() {
self.contacts.push({
firstName: "",
fathersLast: "",
country: ""
});
};
self.removeContact = function(contact) {
self.contacts.remove(contact);
};
};
Any clue on how to implement this validation? I was trying to use jquery validation to do that but I think that is possible with KnockoutJS.
Appreciate any advice.
As stated already, the validation plugin will be the most elegant, less re-inventive solution.
Edit: After commentary implementation utilizing validation plugin
With that aside, you have a couple options.
If you are confident the contact object will always contain only required fields, a not very robust implementation would be iterate over the properties of the contact ensuring each has some value.
A little more robust, but still lacking the elegance of the plugin, implementation would be to maintain an array of required fields and use that array for validation. You can reference my example for this setup. Essentially, each required property is mapped to observables. Changes made to the value of any observable property triggers (via a subscription) a mutation call for a dummy observable that is used in a computed. This is required since a computed can't call valueHasMutated. The mutation call triggers the computed to reevaluate, thus updating the UI.

Getting the last element from a JSON array in a Handlebars template

So, I found that array elements can be accessed in Handlebars using:
{{myArray.2.nestedObject}} and {{myArray.0.nestedObject}}
..to get the third and first elements for instance. (handlebars-access-array-item)
Is there a way to get the last element from an array?
I tried creating a helper for it:
Handlebars.registerHelper("lastElement", function(array) {
return array.last(); //Array.prototype extension
});
...and calling it as follows in the template:
{{lastElement myArray}} or even {{lastElement myArray.lastElement nestedArray}}
Sadly, this doesn't work. Helper functions return strings apparently. What I need is a way to be able to do this even with multi-dimensional arrays.
Should work, I've tested it.
Template:
{{last foo}}
Data:
{foo : [1,2,3,4,5,6]}
Helper:
Handlebars.registerHelper("last", function(array) {
return array[array.length-1];
});
The above piece of code works fine in all the cases. But if the array passed if a null array, possibility of the handlebar function throwing error is there. Instead perform a null check and then return the value accordingly.

JSON error on submit in IE7 (and only IE7): "Cannot deserialize. The data does not correspond to valid JSON."

This is strictly an IE7 problem; IE8 doesn't mind, and neither does FF. Our form throws the following exception when submitting:
Sys.ArgumentException: Cannot deserialize. The data does not correspond to valid JSON.
Parameter name: data
Form header (edited for secrecy; we have a paranoid NDA):
<%using (Ajax.BeginForm("...", "...", FormMethod.Post,
new AjaxOptions {
OnComplete = "OnSaveEditCommunitySuccess",
OnBegin = "OnBegin" },
new { id = "form_Edit...", name = "form_Edit..." }))
I temporarily attached a handler to OnBegin, and looked at the json object that gets passed around. I can guarantee json.get_request().get_body() is identical to what happens in FF, and properly web encoded:
ProfileTabModel.IsEdit=true&ProfileTabModel.HandEnterCommunity=true&ProfileTabModel.CommunityId=26&ProfileTabModel.County=&ProfileTabModel.OrgId=7395& (...)
It might be relevant that this is a large form, and the data sent is at least 2500 bytes. I realize it shouldn't matter in a POSt, and even if it would, it has nothing to do with the reported error, but best mention it, since other forms written on the same pattern have no trouble posting their data.
[EDIT:] When running on debug, IE7 doesn't enter the Action in the controller, while everything else does. Forgot to mention this.
Solution and summary:
The problem occurs when it's time to encode the submit button. Our buttons are generated as <button ... ><span> Submit</span></button>; when it's time to build the request, all other browsers, IE8 included, are smart enough to take only "Submit". IE7 sends all inner HTML. We've had this error in other contexts, but this time I got set on a false path by the JSON error.
I cannot and will not change from <% Http.Button %> to an input; it's beyond me how this could be even considered a viable solution; that extension is used for very good reasons, and if ditching it was an option I wouldn't have asked this question in the first place.
The solution was to add a new filter, extending RequestValidator, and check the request for the offending span tag; if found, replace with the text.
public class HttpRequestValidator : RequestValidator
{
protected override bool IsValidRequestString(
HttpContext context,
string value,
RequestValidationSource requestValidationSource,
string collectionKey,
out int validationFailureIndex)
{
// Check if the Request.QueryString contains a post value of "<span>.....</span>"
if (requestValidationSource == RequestValidationSource.Form
&& !string.IsNullOrEmpty(value) && value.StartsWith("<span>",
StringComparison.InvariantCultureIgnoreCase))
{
var match = Regex.Match(value, #"\A<span>(.*?)</span>\Z",
RegexOptions.IgnoreCase);
if (match.Success && match.Groups.Count > 1)
return base.IsValidRequestString(
context, match.Groups[1].Value,
requestValidationSource,
collectionKey,
out validationFailureIndex);
}
return base.IsValidRequestString(
context,
value,
requestValidationSource,
collectionKey,
out validationFailureIndex);
}
}
You'll also need to add it in web.config; search for httpRuntime and replace it with
<httpRuntime requestPathInvalidCharacters="<,>,*,%,:,\"
requestValidationType=" ... .HttpRequestValidator"
maxRequestLength="40920" maxQueryStringLength="10000"/>
You need to copy/paste your json code into http://jsonlint.com/ to check its validity and see the problem.
Perhaps you'd better to use a normal "input" tag with button type, since it's inside a form. Apparently IE 7 takes the button's inner html as its value, while many other browsers use the "value" attribute.