So I have a modal box that allows the user to edit / save some data.
I just want to add that unlike other Meteor apps, I don't want to save the data straight away - I want the user to fill in all the fields before hitting save where it will save to the database and send to server etc. This is mainly because I want the user to be able to hit the "cancel" button to revert all changes.
I have a drop down box at the start of the form where depending on the value, fields will be shown or hidden
<select class="form-control" id="ddlNewInputType" placeholder="Enter your input type">
<option value="input">Input</option>
<option value="formula">Formula</option>
</select>
And I have a handlebar around a field like this to determine whether I want to show it
{{#if isFormula }}
<div class="row">
<input type="text"
id="txtNewInputFormula" placeholder="Enter formula">
</div>
{{/if}}
With a helper looking like this
isFormula: ->
$('#ddlNewInputType').val() == 'formula'
However, this doesn't work. Aside from when it first loads onto the page, it never hits isFormula, probably because Meteor doesn't consider any of the HTML elements as reactive so it never re-evaluates when the HTML element changes.
What is a suitable way to get around this? Is it possible to make something reactive explicitly in Meteor? I was also considering putting the dropdown list value into a session variable but that just seems messy because I'm going to need to manage this session variable (remember to clear it when the modal box closes etc.)
Your analysis is correct - a reactive variable needs to be involved in order for your helper to reevaluate after changing the select element. The basic strategy looks like:
Initialize a reactive variable when the template is created.
Whenever the select changes, update the reactive variable.
Read the reactive variable in your helper.
Rather than use a session variable, let's use a ReactiveVar scoped to your template. Here's an example set of modifications:
Template.myTemplate.helpers({
isFormula: function() {
return Template.instance().isFormula.get();
}
});
Template.myTemplate.events({
'change #ddlNewInputType': function (e, template) {
var isFormula = $(e.currentTarget).val() === 'formula';
template.isFormula.set(isFormula);
}
});
Template.myTemplate.created = function() {
// in your code, default this to the current value from
// your database rather than false
this.isFormula = new ReactiveVar(false);
};
Remember that you'll need to also do:
$ meteor add reactive-var
See my post on scoped reactivity for a full explanation of this technique.
Related
I'm using Angular 1.4.7. I didn't set up this site so I'm not sure what information I need to give for this. We're rendering some search filters, but whether or not they're enabled is configured by data retrieved from the server, for example:
$scope.searchFields = {
"date": true,
"price": true,
...
};
The HTML for a search field ($scope.filter being an individual set of vars for this particular filter, in this case it's just selecting a maximum numeric value):
<div class="limit-group clearfix">
<span class="detail">Filter Name:</span>
<div class="filter-select-small">
<select name="filter" class="custom-select" ng-if="searchFilters.code">
<option value="">No Max</option>
<option ng-repeat="(key, value) in filter" value="<%key%>"><%value%></option>
</select>
<span class="unavailable" ng-if="!searchFilters.code">Unavailable</span>
</div>
</div>
There are conditions where the available search fields will change. The select fields, for whatever reason, get replaced by a jQuery plugin called selectbox. I have a function resetFilterStyles() which reattaches the jQuery plugin to the fields, but I'm not sure where to fire it.
Inside of methods defined on $scope the data is being updated using $http.get() but running resetFilterStyles() inside of these anonymous functions does not work, presumably because Angular hasn't processed yet that the data has been updated, so the changes that would be performed by resetFilterStyles() are undone by Angular's updates to the DOM.
I've tried setting a $watch handler but this seems to be the wrong place to run my function as it doesn't appear to take, either (and additionally causes grievous errors within Angular). As someone who doesn't use Angular, I'm not sure where to go from here.
Edit
The reset function:
function resetFilterStyles() {
// desktop adv filters
var filterContainer = $('.advance-filter-dropdown');
$('select',filterContainer).selectbox('detach');
$('select',filterContainer).selectbox('attach');
$(".filter-select-small .sbOptions").niceScroll({cursorborder:"",cursorcolor:"#ccc",autohidemode: false});
// mobile adv filters
var filterContainer = $('.filter-wizard-mobile');
$('select',filterContainer).selectbox('detach');
$('select',filterContainer).selectbox('attach');
$(".filter-select-small .sbOptions").niceScroll({cursorborder:"",cursorcolor:"#ccc",autohidemode: false});
}
From the AngularJS perspective, you shouldn't do DOM modifications inside the controller. You should create a directive/component to handle that for you.
But if you really must have that jQuery (legacy projects, for example), you can
put your DOM modification into $timeout:
$http.get("url").then(function() {
$timeout(function() {
// updateDom
}, 0);
});
I'm working with a form and would like to add ng-model on dynamic input elements. I have a scope variable defined as:
$scope.formData = {};
On the page there are a couple of drop-down lists that users can choose an option from and based on those options we are appending some input fields to form body.
formBody.append('<input ng-model="formData.'+obj.Title+'" type="number"></input></br>');
This is not working for me because I'm assuming once the controller runs it can't register any new ng-model. Is there way to add dynamic ng-model or there is a different approach to what I'm trying to do (i.e. build predefined views that can be loaded on the page)?
EDIT:
I have created a jsfiddle that outlines what I'm trying to do - http://jsfiddle.net/k5u64yk1/
If you need to dynamically add html with dynamic bindings that cannot be encapsulated into ng-repeat, ng-if, etc, you have to call $compile on the template once it has been modified to alert AngularJS that it has to reparse the template and initiate a new digest cycle. This will pick up any new ng-model bindings and appropriately tie them to your scope.
HTML:
<div ng-app="MyApp" ng-controller="MyCntrl">
<button ng-click="addInput()">Add Input</button>
<div id="form">
input would go here.
</div>
</div>
JS:
By placing your add input inside of a click event, you avoid an infinite compile loop. Note that this currently resets the state of your form, so if you wanted to work around that you'd need to capture your form state and restore it after compile.
$scope.addInput = function () {
var aForm = (angular.element(document.getElementById('form')));
if ($scope.data["Digital Conversation"][0].MetricType.Title === "Number") {
aForm.append(
'<input ng-model="formData.' +
$scope.data["Digital Conversation"][0].Title.Title +
'" type="number"></input>');
}
$compile(aForm)($scope);
}
You can find the working jsfiddle here: http://jsfiddle.net/k5u64yk1/
I am trying to replace some text in an input field using JS but the view model overrides my commands each time. This is the HTML I start with:
<td class="new-variants-table__cell" define="{ editVariantPrice: new Shopify.EditVariantPrice(this) }" context="editVariantPrice" style="height: auto;">
<input type="hidden" name="product[variants][][price]" id="product_variants__price" value="25.00" bind="price" data-dirty-trigger="true">
<input class="mock-edit-on-hover tr js-no-dirty js-variant-price variant-table-input--numeric" bind-event-focus="onFocus(this)" bind-event-blur="onBlur(this)" bind-event-input="onInput(this)">
</td>
I run this JS:
jQuery('#product_variants__price').siblings().removeAttr('bind-event-focus');
jQuery('#product_variants__price').siblings().removeAttr('bind-event-input');
jQuery('#product_variants__price').siblings().removeAttr('bind-event-blur');
jQuery('#product_variants__price').siblings().focus()
jQuery('#product_variants__price').siblings().val("34.00");
jQuery('#product_variants__price').val("34.00");
And I'm left with the following HTML:
<td class="new-variants-table__cell" define="{ editVariantPrice: new Shopify.EditVariantPrice(this) }" context="editVariantPrice" style="height: auto;">
<input type="hidden" name="product[variants][][price]" id="product_variants__price" value="34.00" bind="price" data-dirty-trigger="true">
<input class="mock-edit-on-hover tr js-no-dirty js-variant-price variant-table-input--numeric">
</td>
The problem is that each time I click the input field the value is reverted to what it was when the page loaded.
I've also tried running the command in the parent td along with my value change, to simulate the editing of a variant and preventing default with no success:
jQuery('#product_variants__price').siblings().bind('input', function (e) {
e.preventDefault();
return false;
});
jQuery('#product_variants__price').siblings().bind('focus', function (e) {
e.preventDefault();
return false;
});
jQuery('#product_variants__price').siblings().focus()
jQuery('#product_variants__price').siblings().val("£34.00");
jQuery('#product_variants__price').val("£34.00");
jQuery('#product_variants__price').siblings().keydown()
Parent td function:
new Shopify.EditVariantPrice(jQuery('#product_variants__price').parent())
So how can I successfully edit this value in the inputs and also update the Shopify view model?
You can try this for yourself by going here:
https://jebus333.myshopify.com/admin/products/2521183043
login jebus333#mailinator.com
password shop1
EDIT: I've tried to find the view model on the page but with no success. Plus, there are no network calls when editing the values in the input fields, leading me to believe the values are being pulled back from somewhere on page.
Try this:
var old = Shopify.EditVariantPrice.prototype.onFocus;
Shopify.EditVariantPrice.prototype.onFocus = function(t) {
this.price = '50.00'; // Use the price you want here
old.call(this, t);
};
jQuery('#product_variants__price').siblings().triggerHandler("focus");
jQuery('#product_variants__price').siblings().triggerHandler("blur");
If it works for you, it's possible that the following will be sufficient:
Shopify.EditVariantPrice.prototype.onFocus = function(t) {
this.price = '50.00'; // Use the price you want here
};
Well, there is a kind of a dirty solution...
First of all you'll need a sendkeys plugin. In fact that means you'll need to include this and this JS libraries (you can just copy-paste them in the console to test). If you don't want to use the first library (I personally find it quite big for such a small thing) you can extract only the key things out of it and use only them.
The next step is creating the function which is going to act like a real user:
function input(field, desiredValue) {
// get the currency symbol while value is still pristine
var currency = field.val()[0];
// move focus to the input
field.click().focus();
// remove all symbols from the input. I took 10, but of course you can use value.length instead
for (var i = 0; i < 10; i++) field.sendkeys("{backspace}");
// send the currency key
field.sendkeys(currency);
// send the desired value symbol-by-symbol
for (var i = 0; i < desiredValue.length; i++) field.sendkeys(desiredValue[i]);
}
Then you can simply call it with the value you wish to assign:
input($("#product_variants__price").next(), "123.00");
I did not really manage to fake the blur event because of lack of the time; that is why I was forced to read the currency and pass .00 as a string. Anyway you already have a way to go and a quite working solution.
Looks like you're trying to automate editing of variant prices of products in Shopify's admin panel.
Instead of playing around with the DOM of Shopify's admin page, I'll suggest using Shopify's bulk product editor which lets you set prices of all variants in a single screen. I feel that you'll have better luck setting the variant prices using JavaScript on the bulk product editor page.
Clicking on the 'Edit Products' button as shown in the screenshot below will open the bulk product editor.
Also check if browser based macro recording plugins like iMacro can be of your help (you can also code macros with JS in iMacro).
I have a list of users on the left with the details on the right.
The right details are handled with a form, and inputs with ng-model. So when I click on a user on the left, I change the selected user and the model automatically shows me the proper details on the right.
I also have a submit button that saves changes to the server.
This all works well, but the problem is that if I change a user's name for example, click on another user, and then come back to the first user, I see the changed name, but the info was never saved on the server. So the client might think that the info was changed on the server, when it wasn't. After I hit refresh, I get the old, server values back.
So how can I revert model changes if I switch the model before hitting save?
By the way, I'm using ng-model instead of using {{ }} in the value field, because AngularJS validators don't work unless you use ng-model
Thanks
Everytime user selects the item in the left, copy that selected object into new variable. You can copy using angular.copy function. Then show that new variable on the right side in the form. Right now changes are "saved" because you are referencing items in your array (so when you change something in form it is changed into this item in array). If you have new variable for selectedItem you won't have this problem any more.
I suggest binding an object to the input fields of your form and using ng-submit to call a function to update your values on the server, much like Nemanja suggested.
I went ahead and made a simple JS Fiddle so you have a small example to structure your code around. Hope it helps!
HTML:
<form ng-model="data" ng-submit="simple.saveName()">
Name: {{ simple.data.name }}
<br />Twitter: {{ simple.data.twitter }}
<br />
<br />New name:
<input type="text" ng-model="simple.newName" />
<br />
<button>Submit</button>
</form>
JavaScript:
(function () {
angular
.module('app', [])
.controller('simpleController', SimpleController);
function SimpleController() {
var vm = this;
// Public stuff.
vm.data = {
name: 'Joe',
twitter: '#martellaj'
};
vm.newName;
vm.saveName = saveName;
// Private stuff.
function saveName() {
console.log('Saving name...');
vm.data.name = vm.newName;
};
};
})();
I have a simple Angular app which has some data, and an input box to filter that data.
<input class="form-control" ng-model="filters.generic" />
<h3>{{filteredUsers.length}} filtered users</h3>
<ul>
<li ng-repeat="user in filteredUsers = (data.userData | filter:filters.generic )">
{{user.registration.firstName}}
</li>
</ul>
The 'filteredUsers' variable stores the users with the applied filter. That all works absolutely fine. However, I'd like to be able to add a watch to execute a particular function whenever this 'filteredUsers' variable changes (whenever the user types anything in the input box).
I do not want to simply add a method to ng-change on the input, because I want to introduce other filters elsewhere.
I thought that I could do this in my controller with
$scope.$watch('filteredUsers', function () {
alert('something');
});
This executes once, when the application loads, and then never again. What must I do to make this watch for changes to filteredUsers?
You might need to use $watchCollection() instead of $watch()
Keep in mind that the filter on an ng-repeat could be running way more frequently than you need or expect it to, and adversely affect the performance of your code.
Even though you said you don't want to use ng-change, I think you should reconsider that and only update your array when needed.
<input class="form-control" ng-model="filters.generic" ng-change="genericFilterChange()" />
var updateFilteredUsers = function(){
$scope.filteredUsers = $filter('filter')($scope.data.userData, $scope.filters.generic);
alert('something');
};
var genericFilterChange = function(){
updateFilteredUsers();
updateOtherFilteredThings();
};