Is it possible to change the context of an Ember view, specifically as regards the valueBinding? I have a matrix of text inputs with rows representing categories and columns representing days of the week. I don't want to save empty values or 0 values, but each input has some other data specific to it that needs to be saved if the user enters hours in that input. So, when generating the form, I create an array of objects, each of which is simply a javascript hash (if no existing entry exists for that field) or an ember-data record (if an entry already exists) and use each of these objects as the context for the view.
So, the idea is that if the user enters a zero or an empty string in a field with an existing value, that record is deleted and the context of the view is replaced with a simple hash that still contains the other relevant pieces of info for that field, in case the user comes back and sets that value again. Conversely, adding input to a field that was previously empty would create a new ember-data record using the values from the attached hash and the value of the input.
I've gotten as far as deleting existing records, which works, but the valueBinding for the text input remains bound to the record that's slated for deletion. I would like to change the context of the field to the placeholder hash immediately. If I commit the transaction and come back to that screen, the record has been deleted and the context is now a newly generated placeholder, but I would like to change the context of the TextField view (which I've extended) as soon as the value changes to zero/null. I've attempted using this.set('context') and this.set('valueBinding'), but neither work. Here's the code for my view so far. Can anyone help me out, or suggest a better alternative?
App.HourInputView = Ember.TextField.extend({
type: 'number',
init: function(){
this._super() ;
var context = this.get('context') ;
var ph = moment(this.get('context.cal_date')).format("dd") ;
this.set('placeholder',ph) ;
},
change: function(){
var val = this.get('value') ;
if (this.contextIsRecord() && (val===0 || val==='0' || val==='')) {
var record = this.get('context') ;
var new_ctx = this.get('context').getProperties('user','site','supervisor','category','cal_date') ;
new_ctx.total = null;
this.set('context',new_ctx) ;
this.set('valueBinding',"new_ctx.total") ;
record.deleteRecord() ;
}
},
//--
contextIsRecord: function() {
var ctx = this.get('context') ;
return (typeof ctx.deleteRecord === 'function') ;
}
}) ;
Holy cow, was I going about this one wrong. Walked away, had some lunch and came back and came up with a better solution. Instead of using a combo of actual Record objects and dummy hashes, I create a record object for each input and observe changes on each input. Any changes get added to a custom transaction. When the user clicks the save button, I commit the custom transaction and roll back the default transaction to get rid of all the irrelevant Record objects.
Related
I'm building a web app that lets the user curate a double-feature film showing. The user enters a title, a blurb, and two film titles. On a submit button a function is called that displays the user-submitted title, user-submitted blurb, and makes two separate API calls to retrieve movie information on each respective feature.
I'm trying to establish something of a favorites functionality that utilizes localStorage. Conceptualizing the solution, let alone implementing it may be my first mistake, so I'm open to alternative suggestions, but I believe the best way to do this is to capture each search field value (title, blurb, movie_1, movie_2), store these four string values in an object and then push that object to an array, placing each object into localStorage and then getting each object from localStorage later on with a button click.
I'm able to capture these items, store them in localStorage and dynamically generate buttons that when clicked populates the four search field values back into the respective search fields, allowing the user to click the submit button again which runs the api calls and displays all of the content (again: title, blurb, movie_1, movie_2).
My problem is looping through the objects and grabbing the different search field data values. I suppose the problem is in assigning a name or key to the different objects that I'm looping through in the array and then accessing the correct values from the appropriate button through localStorage. I seem to be setting the same localStorage object (or rewriting it) and accessing it over and over, as opposed to setting a new localStorage object and getItem'ing the right one.
I'll provide some code snippets below, but it might be easier to peruse my GitHub repo: https://github.com/mchellagnarls/double_feature
If you look at the repo, latest code is found in index_test.html and app_test.js, whereas a previous version without any of the broken favorite functionality is found in index.html and app.js.
Some code snippets:
// logic to capture search field values and to eventually display them as buttons
// empty array
var dfArray = [];
// object to hold each of the string values to populate the search fields
var doubleFeature = {
feature_1: movie_1,
feature_2: movie_2,
DFTitle: title,
DFBlurb: blurb
}
dfArray.push(doubleFeature);
for (var i = 0; i < dfArray.length; i++) {
localStorage.setItem("df", JSON.stringify(dfArray[i]));
var button = $("<button>");
button.addClass("df-favorite-button");
button.text(title);
$("#buttons-view").append(button);
}
$(document).on("click", ".df-favorite-button", function() {
event.preventDefault();
var savedDF = JSON.parse(localStorage.getItem("df"));
$("#movie-input-1").val(savedDF.feature_1);
$("#movie-input-2").val(savedDF.feature_2);
$("#df-title-input").val(savedDF.DFTitle);
$("#df-blurb-input").val(savedDF.DFBlurb);
})
Thanks for any help. I'm learning web development and I may be overcomplicating things or missing out on an easier way to think about it and/or solve it.
Instead of this loop:
for (var i = 0; i < dfArray.length; i++) {
localStorage.setItem("df", JSON.stringify(dfArray[i]));
var button = $("<button>");
button.addClass("df-favorite-button");
button.text(title);
$("#buttons-view").append(button);
}
I would place the whole array into local storage
localStorage.setItem("df", JSON.stringify(dfArray));
Also then you have to decide which object to get from array in click function
$(document).on("click", ".df-favorite-button", function() {
event.preventDefault();
var id = -1; // which one
var savedDF = (JSON.parse(localStorage.getItem("df")) || []).find(pr => pr.id === id);
$("#movie-input-1").val(savedDF.feature_1);
$("#movie-input-2").val(savedDF.feature_2);
$("#df-title-input").val(savedDF.DFTitle);
$("#df-blurb-input").val(savedDF.DFBlurb);
})
I have a form that uses server-side validation and coercion.
In Vue, the state of the form fields is held in an object called instance, on the data object. Each field's value is represented by a property of instance.
onChange of any field, instance is posted to an API method that returns validation results and a coerced dataset (coercion does things like adding spaces to phone numbers, capitalising postcodes etc.).
Vue takes the response and iterates through the coerced data, replacing the properties of instance. If a field has not yet been reached by the user it is skipped (There is a reached object that keeps track of which fields the user has made it to).
The issue that I'm having is that occasionally (when entering data extremely quickly from one field to the next) the input of the current field gets cleared when the coerced data is returned from the previous one.
Initially I thought that there must be some issue with the reached logic, and that the null data returned for the field that the user is working on is overwriting the current input. But this is not the case; I can see in my logs that fields are being skipped yet the input is still clearing.
I'm starting to think that this might be a bug with Vue. Or at least, something specific to how Vue handles the data/dom elements that I need to account for. Is there a way that setting instance.foo could cause instance.bar to be reset?
//this is called onChange for any field.
change: function(e) {
this.$set(this.instance, e.name, e.value);
this.setReached(e.name);
this.validate(true);
},
validate: function(reachedOnly) {
axios.post(this.validateUrl, this.getFormData(false)).then(response => {
this.allErrors = response.data.errors;
this.setFormData(response.data.values, reachedOnly);
this.fieldNumberValidated = this.fieldNumberReached;
});
},
setFormData: function(data, reachedOnly) {
for (var fieldName in this.fieldNames) {
var value = data[fieldName];
if(reachedOnly && !this.reached[fieldName]){
console.log('skipping - '+fieldName);
continue;
}
if (value && value.date) {
value = value.date.replace(/\.\d+$/,'');
}
this.$set(this.instance,fieldName,value);
}
},
* UPDATE: *
I think I know what's happening now.
Field A triggers change()
Data gets sent for validation
User starts inputting into field B
Validated data gets returned. And set on this.instance.
Vue skips field B because it isnt in this.reached
BUT this.instance is being updated and redrawn.
Field B may have text entered in its input but it hasn't been added to this.instance because it hasn't triggered change() yet. So this.instance is redrawn based on field B having no value, which in turn updates the input and wipes whatever may have been in there.
This isn't a full answer but just some thoughts.
I'm not certain about why a field is being cleared, however I would like to point out a concurrency issue you may have. If you're calling the API for each keypress, you're not guaranteed that they will respond in the correct order, and it could be that you are setting the form data to an old validation response which would cause you to lose any text entered into the textbox since the request was fired. Also it's generally a good idea not to spam the server with too many requests.
At a minimum you should probably debounce the API calls, or use blur instead of change event, or you could implement some logic that cancels any pending validation request before firing another one.
Is there any particular reason why you are using this.$set? It should only be used if you're adding a property to an object.
Initially I thought that there must be some issue with the reached logic, and that the null data returned for the field that the user is working on is overwriting the current input. But this is not the case; I can see in my logs that fields are being skipped yet the input is still clearing.
It might be better to log when you set the data, instead of when you skip. The issue is some fields are being cleared, so log every time they are set so you can identify times when the field is being set when it shouldn't be.
Is there a way that setting instance.foo could cause instance.bar to be reset?
Not that I'm aware of. It would help if you can provide a MCVE.
I eventually solved this by having 2 different events on my input fields - one for input that updates instance and another on blur that sends the validation request.
change: function(e) {
this.validate(true);
},
input: function(e) {
this.$set(this.instance, e.name, e.value);
},
This ensures that the properties of instance are always in line with their related input fields, and so nothing gets erased when instance is redrawn.
I have this function which I believe is following this process:
function verify(){
$.get("map_process.php", function (data) {
verified = $(data).find("marker").eq(-1).attr('verification');
});
}
Get data from php file/db
In the db, find the table "marker"
Find the last record in the table marker
Assign the value of the 'verification' column to the variable verified
This is doing what I want (kind of) but I need to be able to specify what record to get the 'verification' value from, but not by it's position in the table (as more records will be added and the above will just get the last record regardless). Is there another method that is kind of like .eq(x) but will allow me to specifically select a record based on another attr in that record.
eg. Say I want to find the verification value for record 1 through an event listener, and then find the verification value for record 6 through a different event listener.
I have a variable which can distinguish what row I want to get, but how can I incorporate this into the statement above. (i'm thinking instead of .eq(-1)
You can use filter() which can contain as many conditions as you need.
$.get("map_process.php", function (data) {
var myVariable = $(data).find("marker").filter(function(elementIndex, element){
return $(this).attr('someOtherAttribute') === 'valueWanted';
}).attr('verification');
});
Since I'm really not sure what the data looks like or what attribute you need the above is only a guess at how you would need to implement
See filter() API docs
How can I get the element id of an item in a store range on ExtJS4 based on a property of the item? For instance, I am getting the store as follows:
var combobox = Ext.ComponentQuery.query('[xtype=mycombobox]')[0];
var items = combobox.getStore().getRange();
I want to jump to the correct item in the combobox based on a productid that a user selects elsewhere:
combobox.select(elementid);
I am just missing the logic that lets me say
elementid = items.getWhere('prodid', 'productid'); // Or however its actually done.
This is what I ended up coming up with, which actually required two seperate calls. Not sure if this is the most efficient way to do it but it seems to work.
First, I need to get the model that has a productid that equals value:
var model = combobox.getStore().findRecord('productId', value);
Then, I need to figure out what the index of that model is in the overall store:
var index = combobox.getStore().indexOf(model);
Then I can take the index and apply it to back to the combobox:
combobox.select(index);
On my page I have 2 trees.
There is form on the page. One tree is shown on the page and another inside the form.
I have create trees as below:
var myTree1 = Ext.create('Ext.tree.Panel', {
store: store, //where store is hardcoded json tree data
....
});
similarly myTree2 is declared with different store. It is shown inside a form.
When I select on any node on myTree2 and click on create button then I must be able to add a new leaf node inside myTree1 at the same index.
Help I need are:
1. How to get index of selected node.
2. How to go to the index in myTree1, should be equal to the selected index myTree2.
3. How to add a leaf node at specific index.
For reference:
Ext JS 4 TreePanel documentation
Ext JS 4 NodeInterface documentation
The select event provides both the index and the record. If you need general access to it, myTree1.getSelectionModel().getSelection() will return an array of records, which you can then check against the store to get the index.
I'm assuming you want to compare the selected records on both trees. Given the sample in #1, try this:
var tree1rec = myTree1.getSelectionModel().getSelection[0],
tree2rec = myTree2.getSelectionModel().getSelection[0];
// Perform comparison logic here
// Note that if you're using two different stores, you can't
// just do an equality test. You'll probably have to compare
// individual store values.
Nodes are inserted relative to existing nodes on the tree. So if you have a button click event:
function onButtonClick(button, eOpts) {
var rec = myTree1.getSelectionModel().getSelection[0];
if (rec) {
var childNode = rec.createNode(/* some object here */);
rec.appendChild(childNode);
}
}
The details will vary depending on your actual data, but this is the general pattern.