I have an app that deals with ordering coffee. The coffee store has a table that shows drink types by size, and they can click on a given drink/size and edit data about that drink/size combo, such as the price.
Before, there was a set list of coffee drinks (mocha, cappuccino, etc) and I was able to gasp hardcode the drinks and get by this bug. However, things have changed and now the store can add custom drinks, meaning that I can no longer hardcode the drinks and I need to get the store drinks from the API.
This project is using Ember 1.13, and I'm setting the drinks in the setupController in the route. In this example I'm not going to be showing the custom drinks, the problem is reproducible just by using the default drinks.
model: function() {
let myStoreId = this.controllerFor('application').get('myStore.id');
return Ember.RSVP.hash({
myStore: this.store.find('store', myStoreId),
storeDrinks: this.store.find('store-drink', {'store':myStoreId}),
...
});
},
setupController: function(controller, model) {
// we have some set drinks that will be for every store, although they can set inactive
let defaultDrinks = [
"Americano", "Capuccino", "Drip", "Espresso", "Latte", "Mocha", "Tea"
];
let drinksArray = [];
let drinkType, keyName;
let pluralize = function(string){
// return plural and lower case for a drink type
let lower = string.toLowerCase();
let plural = lower + "s";
return plural;
};
for (let i = 0; i < defaultDrinks.length; i++) {
drinkType = defaultDrinks[i];
keyName = pluralize(drinkType);
// when we define like this, there are bugs editing in the template. But we
// can loop though all the drinks by type. I can get a list of custom drinks
// from the API and add those into the loop.
drinksArray[keyName] = this.store.filter('store-drink', function(drink) {
return drink.get('type') === drinkType;
});
}
// when we define like this (hardcode), it works fine in template but we
// can't keep doing this because with custom drinks we don't know the type
// when we get the above loop working, this code will be gone, but it shows
// what works in the template to edit drinks.
let cappuccinos = this.store.filter('store-drink', function(drink) {
return drink.get('type') === "Cappuccino";
});
...
console.log(drinksArray["mochas"], cappuccinos);
controller.setProperties({
'mochas': drinksArray["mochas"],
'cappuccinos': cappuccinos,
...
'myStore': model.myStore,
});
}
There's the setup in the route. Now in the template I have an input that is tied to the drink value. When they click on one of the drink/size combos, it opens a div that has the detailDrink object. {{input value=detailDrink.price ... }}.
When the drink uses the drinkList in the form of cappuccino everything works fine. When the drink uses the drinkList in the form of drinksArray["mochas"] then when the input changes, there are various bugs. I don't believe the details of this part to be significant but sometimes it deletes the cell value, sometimes it doesn't reflect the change, and sometimes it binds multiple cells to the same value. The issue is that when using the data from an array (such as with mochas) this bug is there, and when using the hardcoded value (such as with cappuccinos) the drink data can be updated correctly.
Another thing to note is that in the console.log(drinksArray["mochas"], cappuccinos); above, both objects appear to be the same, other than of course one is a list of cappuccinos and the other is a list of mochas.
I've literally been stuck on this for months off-and-on, and have tried so many things and have isolated it down to this.
EDIT ADDITION:
You might think "how will this help your problem"? My idea is to have an array of objects such as:
[{
'drinkSet': cappuccinos,
'drinkType': 'Cappuccino',
}, {
'drinkSet': mochas,
'drinkType': 'Mocha',
},
{
'drinkSet': myCustomWhiteChocolateThunder,
'drinkType': 'White Chocolate Thunder',
},
...
]
and then loop through my template table rows with each drink type
<table class="table table-striped menu__drink-table hidden-xs">
<thead>
<tr>
<th>Drink</th>
<th>8oz</th>
<th>12oz</th>
<th>16oz</th>
<th>20oz</th>
<th>24oz</th>
<th>32oz</th>
</tr>
</thead>
<tbody>
{{#each drinkSetObject in drinkSets}}
{{joe-menu-row drinkSet=drinkSetObject.drinkSet type=drinkSetObject.drinkType detailDrink=detailDrink}}
{{/each}}
</tbody>
</table>
I had this before, but isolated the problem down to the fact that when the drinks were a value in an array for some reason they don't work as they do when declaring the variable directly.
I had similar problem resolving promises in setup controller. Seems that promise in an array does not resolve so you can't get the data in the template.
Please try the next and let me know:
for (let i = 0; i < defaultDrinks.length; i++) {
// can't be method variables since will be used in the promise
let drinkType = defaultDrinks[i];
let keyName = pluralize(drinkType);
this.store.filter('store-drink', function(drink) {
return drink.get('type') === drinkType;
}).then(function(result) {
controller.set(keyName, result);
}, function(error) {
//TODO: handle error
});
}
Also, use the ember inflector's pluralize function:
const { Inflector: { inflector } } = Ember
let keyName = inflector.pluralize(drinkType);
Hope it helps
ps: dont forget to remove the controller.setProperties setup
Related
Requirement: I want to update the value of a custom attribute (name: badges) (type: enum-of-strings) for a Product via code. I want to set the value "bestSeller" as selected. How should I do that update because the code below is not working?
Screenshot of the Custom Attribute in Business Manager
Code snippet:
function updateBestSeller() {
var ProductMgr = require('dw/catalog/ProductMgr');
var Site = require('dw/system/Site');
var UUIDUtils = require('dw/util/UUIDUtils');
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
var currentSite = Site.getCurrent();
var bestSellerOrderUnits = Object.hasOwnProperty.call(currentSite.preferences.custom, 'bestSellerOrderUnits') ? currentSite.getCustomPreferenceValue('bestSellerOrderUnits') : 0;
try {
Transaction.wrap(function () {
var count = 1;
var products = ProductMgr.queryAllSiteProducts();sni
var HashSet = require('dw/util/HashSet');
var badges = new HashSet();
if (products.count > 0) {
while (products.hasNext() && count < 5) {
var product = products.next();
var badges = [];
badges.push('bestSeller');
if (Object.hasOwnProperty.call(product.custom, 'badges')) {
product.custom.badges = badges
}
count++;
Logger.debug('{0}',product.ID);
}
}
products.close();
});
} catch (ex) {
Logger.error(ex.toString());
return new Status(Status.ERROR, 'ERROR', 'UPDATE failed');
}
return new Status(Status.OK, 'OK', 'UPDATE successful');
}
I think what is probably happening here is that your attempt to validate that a product has a badges key in the product.custom attribute collection is failing. This prevents the update from occurring.
I suggest removing the condition around the following line:
product.custom.badges = badges;
If that line were to not execute, then the update to the database would never occur.
The way custom attributes work is that they will never exist until a value is set for that attribute for a given persistent object. (eg: Product) So checking to see if it exists via something like: 'badges' in product.custom (which is the recommended way) will often be false even when the custom attribute definition exists because many products have never had a badge value set. Once an object has had a value set to a particular custom attribute even if it is now set to null then it will exist.
Additionally, there are some other issues with the code that may be causing problems. One example is defining the badges variable twice within the same scope. Another example is sni that is placed at the end of the line where you define the products variable. This is likely causing an error in your code. Finally, it is a good practice to avoid the use of the queryAllSiteProducts method if you can. An alternative may be to use the ProductSearchModel instead; This may not always meet your need, but it is a good idea to rule it out before resorting to queryAllSiteProducts for performance reasons.
Something else to consider is that if badges currently has any selected values, you'll be overwriting those values with the code you have today. Consider setting badges initially to [] then check to see if there is a value for that product by doing:
if ('badges' in product.custom && !empty(product.custom.badges) {
badges = product.custom.badges;
}
I have a problem with initialization data before component is created. My actual question depends on that problem: I'm losing reactivity in one of my data properties because I initialize it in lifecycle hook. But I don't know how to initialize an array from data(){} with a length which I receive from props. If I make it in lifecycle hook, then I'm losing reactivity, as I metioned before.
Here is some more details about my component:
In my Vue.js learning I'm trying to implement a stepper component. I decided to make it a little more dynamic and to be with a flexible size. So in my props of stepper component I receive an Object with such structure:
stepperData: {
steps: 3, //maybe later I'll add more options to stepperData, so I decided to implement it as an Object, not Array of content.
content: [
{
header: "Stepper #1",
text: "Hello World 1!"
},
{
header: "Stepper #2",
text: "Hello World 2!"
},
{
header: "Stepper #3",
text: "Hello World 3!"
}
]
}
Than in my stepper component I am using a steps field to determine a length of another array which hold data about marked or unmarked steps. Here is a code which I am using to initialize that array of marked steps:
methods: {
initializeMarkedSteps() {
this.markedSteps = [];
for (var i = 0; i < this.dataStepper.steps; i++) {
this.markedSteps[i] = false;
}
}
},
created: function() {
this.initializeMarkedSteps();
}
markedSteps is an empty array in data(){}
So, after that, I had an array of false values. In my template I have a v-bind:class
<div class="circle" v-bind:class="{markedCircle: markedSteps[s]}">
Thanks to it all of the steps are unmarked and they can became marked after user clicks "next" button.
<my-btn #click="nextStep">Next</my-btn>
my-btn is my wrapper component for simple button.
Code in nextStep():
nextStep() {
for (let i = 0; i < this.dataStepper.steps; i++) {
if (this.markedSteps[i] === false) {
this.markedSteps[i] = true;
console.log(this.markedSteps);
return;
}
}
}
BUT, when I click button, markedCircle class is not assigned as I expect despite the fact, that acual value of markedSteps[i] was changed to true after button was clicked.
I am very frustrated with this stuff with which I am so messed up. Any help will be appreciated. I have already checked docs on this theme and also I've read "Reactivity in Depth" section but I didn't saw an answer.
There are multiple problems
In your examples you don't show how you initialize your data() but assuming from the code this.markedSteps = []; in initializeMarkedSteps I think you have no markedSteps in data(). That's problem number 1. Properties in data are only reactive if they existed when the instance was created (add markedSteps: [] into data())
Due to limitations in JavaScript, Vue cannot detect changes to an array when you directly set an item with the index - use Vue.set(this.markedSteps, i, true) instead
I just started learning RxJS. One thing I have tried to do without much luck is, figuring out how to search/filter a Subject, or create an observed array that I can search on.
I've tried piping and filtering a Subject and BehaviorSubject, but the values in the predicate are RxJS specific.
From what I've read on various posts, the way is to observe an array to use a Subject.
I can easily have two variables, one array and the Subject, and search the array. But I'd like to accomplish this one variable.
In Knockout its possible to search an observed array.
Is this possible in RxJS?
Thanks in advance.
Example:
layers: Rx.Subject<any> = new Rx.Subject<any>();
toggleLayer (layerId, visible) {
//find layer we need to toggle
// How do I search the subject to get the values added in next()?
// tried from(this.layers), pipe does not fire
const source = of(this.layers);
const example = source.pipe(filter((val, index) => {
//val is subject, and only iterates once, even if more than one value in subject
// tslint:disable-next-line:no-debugger
debugger;
return false;
}));
const sub = example.subscribe((val) => {
// tslint:disable-next-line:no-debugger
debugger;
});
}
private addLayer = (layerName, layerObj, layerType) => {
// component class is subscribed to layers subject. Update UI when layer is added
this.layers.next({
layerId: this.layerId,
name: `${layerName}_${this.layerId}`,
layerObj: layerObj,
visible: true,
layerType: layerType
});
}
I'm not 100% clear on the specifics of your ask, but maybe this example will help you.
const filterSubject = new BehaviorSubject<string>('b');
const dataSubject = new BehaviorSubject<string[]>(['foo', 'bar', 'baz', 'bat']);
const dataObservable = combineLatest(filterSubject, dataSubject).pipe(
// given an array of values in the order the observables were presented
map(([filterVal, data]) => data.filter(d => d.indexOf(filterVal) >= 0))
);
dataObservable.subscribe(arr => console.log(arr.join(',')));
// bar, baz, bat
Using combineLatest, you can have the value in dataObservable updated whenever either your filter value or your data array changes.
I am building a web application using JavaScript and firebase.
everytime I click on the "remove" paragraph tag, I can only remove the most recent added item, but cannot remove the rest.
Example: if I have 9 items, and I just added a 10th item, I can only remove the 10th item, but not the other 9 items. I basically can only remove items in a backwards order, as opposed to being able to remove items in any order of my choice.
Here is my code:
function displayFav(){
const dbRef = firebase.database().ref();
dbRef.on("value", (firebaseData) => {
// empty out element before adding new updated firebase data so there are no repeated data
document.getElementById("displayUsername").innerHTML = "";
let accounts = [];
const accountData = firebaseData.val();
for (let itemKey in accountData) {
accountData[itemKey].key = itemKey;
accounts.push(accountData[itemKey])
const key = accountData[itemKey]["key"];
const password = accountData[itemKey]["password"];
let user = accountData[itemKey]["username"];
// code where I try to render results from page
document.getElementById('displayUsername').innerHTML += `
<li> Username: ${user} Password: ${password}</li>
<p id=${key}>Remove</p>
`;
// code where I try to remove the item
document.getElementById(key).addEventListener("click", function(){
removeItem(key)
})
}
});
}
This is my function to remove the firebase data:
function removeItem(itemToRemove){
const dbRef = firebase.database().ref(`${itemToRemove}`);
dbRef.remove();
};
What can I change or add to my code that will allow me to remove items in any order I want, instead of letting me delete only the most recent items?
Not trying to be rude, this is just a tip: Please indent/format your code well so that people can understand it without having to format it themselves. Usually when people see large code that's not well formatted, it throws them off (sometimes including me), and therefore they wouldn't want to continue to read or help you with the question.
Suppose you have a table called posts, and the structure looks like this:
that is:
{
<postId>: {
date: 1518925839059,
message: 'some message'
},
<postId>: {
date: 151892583967,
message: 'some message'
},
...
...
}
Suppose you want to delete the extra property of the first post, as circled in the picture, you must know the full path of the data:
firebase.database().ref('posts/-L5b1EZ1L_FycluzTIn8/extra').remove();
If you want to delete everything in post 1:
firebase.database().ref('posts/-L5b1EZ1L_FycluzTIn8').remove();
Or, if you want to delete every single post:
firebase.database().ref('posts').remove();
Could you please find what I'm doing wrong with this array filter. Fiddle Here
I've been working on it, and making very slow progress. I checked on a lot of samples but not able to find my issue.
Thanks
//This is the part I'm not able to fix
self.filteredPlaces = ko.computed(function() {
var filter = self.filter().toLowerCase();
if (!filter) {
ko.utils.arrayForEach(self.placeList(), function (item) {
});
return self.placeList();
} else {
return ko.utils.arrayFilter(self.placeList(), function(item) {
var result = (item.city().toLowerCase().search(filter) >= 0);
return result;
});
}
});
You did not data-bind filter to any input. You used query instead.
Change your filter value to use the query observable:
var filter = self.query().toLowerCase();
I think I know what you're trying to accomplish so I'll take a shot. There are a few things wrong with this code.
foreach in knockout accepts an array not a function.
http://knockoutjs.com/documentation/foreach-binding.html
I think you're trying to hide entries that don't contain the text in the search box. For that you need the visible binding. I re-factored your code to the sample below.
<div data-bind="foreach: placeList" class="alignTextCenter">
<p href="#" class="whiteFont" data-bind="text: city, visible: isVisible"></p>
</div>
I added isVisible as an item in your array, and an observable in your class.
var initialPlaces = [
{"city":"Real de Asientos","lat":22.2384759,"lng":-102.089015599999,isVisible:true},
{"city":"Todos Santos","lat":23.4463619,"lng":-110.226510099999,isVisible:true},
{"city":"Palizada","lat":18.2545777,"lng":-92.0914798999999,isVisible:true},
{"city":"Parras de la Fuente","lat":25.4492883,"lng":-102.1747077,isVisible:true},
{"city":"Comala","lat":19.3190634,"lng":-103.7549847,isVisible:true},
];
var Place = function(data) {
this.city = ko.observable(data.city);
this.lat = ko.observable(data.lat);
this.lng = ko.observable(data.lng);
this.isVisible = ko.observable(data.isVisible);
};
Lastly, you want to subscribe to the changes of "query" since it's on your text box so that the list updates when the text box changes. It's the self.query.subscribe line in my fiddle. I apologize about the formatting, I tried several times and could not get it to work.
Working fiddle here