I want to remove ko observable array element by specific field value. I tried one solution. But, there are something missing. It's not working.
customOptionVal : ko.observableArray([])
customOptionVal is ko observableArray and output of that is :
Color: [0: {sub_color: "Red", sub_id: "options_3_2", is_checked: true}
1: {sub_color: "Green + $250.00", sub_id: "options_3_3", is_checked: true}]
Size: {sub_size: "L", sub_id: "options_2_2", is_checked: true}
Now, I want like that if sub_id = options_3_2 then, it will remove from Color element on the base of sub_id.
I tried this below solution. But, it's not working :
$.each(self.customOptionVal()['Color'], function( key, val ) {
if(self.customOptionVal()['Color'][key].sub_id == 'options_3_2') {
self.customOptionVal.remove(self.customOptionVal()['Color'][key]);
}
});
The following snippet removes from the customOptionVal observableArray itself -
self.customOptionVal.remove(function(option) {
return ko.utils.arrayFilter(option.Color, function(color) {
return color.sub_id === subId;
});
});
However, if you only want to remove from the Color array (which is not an observableArray), use the following snippet -
self.customOptionVal().forEach(function(option) {
var index = option["Color"].findIndex(function(y) {
return y.sub_id === subId;
});
if (index > -1) {
option["Color"].splice(index, 1);
}
});
Fiddle
I found better way that :
As see in screenshot, create one ko observable array and set Color value in that ko.observableArray
custom_option_select_text_arr = ko.observableArray([])
.....
this.custom_option_select_text_arr.push({sub_color: "Red", sub_id: "options_3_2", is_checked: true});
this.customOptionVal()['Color'] = this.custom_option_select_text_arr();
Now, for remove element :
self.custom_option_select_text_arr.remove(self.custom_option_select_text_arr()[0]);
self.customOptionVal()['Color'] = this.custom_option_select_text_arr();
Related
I've created a qooxdoo list with customized items containing checkbox and label.
My problem is: when I check the check box, it gets bigger which gives an ugly user experience. Also when I check some first items and scroll down, I see many items checked which should be unchecked by default.
Here's the code that someone can paste into play ground for qooxdoo:
// Create a button
var button1 = new qx.ui.form.Button("click to see list!", "icon/22/apps/internet-web-browser.png");
// Document is the application root
var doc = this.getRoot();
// Add button to document at fixed coordinates
doc.add(button1,
{
left : 100,
top : 50
});
var popup;
// Add an event listener
button1.addListener("execute", function(e) {
if (!popup) {
popup = new myApp.list();
}
popup.placeToWidget(button1);
popup.show();
});
/*
* class: list inside popup.
*/
qx.Class.define("myApp.list",
{
extend : qx.ui.popup.Popup,
construct : function()
{
this.base(arguments);
this.__createContent();
},
members : {
__createContent : function(){
this.set({
layout : new qx.ui.layout.VBox(),
minWidth : 300
});
//prepare data
var zones = [];
for (var i=0; i<100; i++){
zones.push({"LZN" : "ZONE " + i, "isChecked" : false});
}
var lstFences = new qx.ui.list.List();
this.add(lstFences, {flex : 2});
var delegate = {
createItem : function() {
return new myApp.customListItem();
},
bindItem : function(controller, item, id) {
controller.bindProperty("isChecked", "isChecked", null, item, id);
controller.bindPropertyReverse("isChecked", "isChecked", null, item, id);
controller.bindProperty("LZN", "LZN", null, item, id);
}
};
lstFences.setDelegate(delegate);
lstFences.setModel(qx.data.marshal.Json.createModel(zones));
lstFences.setItemHeight(50);
}
}
})
/**
* The custom list item
*/
qx.Class.define("myApp.customListItem", {
extend : qx.ui.core.Widget,
properties :
{
LZN:
{
apply : "__applyLZN",
nullable : true
},
isChecked :
{
apply : "__applyChecked",
event : "changeIsChecked",
nullable : true
}
},
construct : function()
{
this.base(arguments);
this.set({
padding : 5,
decorator : new qx.ui.decoration.Decorator().set({
bottom : [1, "dashed","#BBBBBB"]
})
});
this._setLayout(new qx.ui.layout.HBox().set({alignY : "middle"}));
// create the widgets
this._createChildControl(("isChecked"));
this._createChildControl(("LZN"));
},
members :
{
// overridden
_createChildControlImpl : function(id)
{
var control;
switch(id)
{
case "isChecked":
control = new qx.ui.form.CheckBox();
control.set({
padding : 5,
margin : 8,
value : false,
decorator : new qx.ui.decoration.Decorator().set({
width : 2,
color : "orange",
radius : 5
})
});
this._add(control);
break;
case "LZN":
control = new qx.ui.basic.Label();
control.set({allowGrowX : true});
this._add(control, {flex : 2});
break;
}
return control || this.base(arguments, id);
},
__applyLZN : function(value, old) {
var label = this.getChildControl("LZN");
label.setValue(value);
},
__applyChecked : function(value, old)
{
var checkBox = this.getChildControl("isChecked");
console.log(value, old);
checkBox.setValue(value);
}
}
});
There are two problems here:
The first one is the fact that by creating the checkbox as a subwidget via _createChildControlImpl makes the checkbox loosing its appearance (in sense of qooxdoo theme appearance) leading to the lost minWidth attribute which makes the checkbox having a width of 0 when unchecked and a width which is needed to show the check mark when it's checked. The solution here is to add an appearance to the myApp.customListItem class like this:
properties : {
appearance: {
refine : true,
init : "mycustomlistitem"
}
}
and afterward add a corresponding appearance to your theme:
appearances :
{
"mycustomlistitem" : "widget",
"mycustomlistitem/isChecked" : "checkbox"
}
You could also add all the styling you've done when instantiating the checkboxes (orange decorator etc.) within the appearance definition.
The second problem is that you’ve defined only a one way binding between the checkbox subwidget of the custom list item and its "isChecked" sub widget. You need a two way binding here, thus if the value of the property "isChanged" changes it’s value it prpoagates that to the checkbox and vice versa.
I've modified your playground sample accordingly by creating the missing appearance on the fly and by creating a two way binding between the checkbox and the list items “isChecked” property. Note that I've created the list directly in the app root for simplicity:
https://gist.github.com/level420/4662ae2bc72318b91227ab68e0421f41
I'm trying to detect if a Model Data has not been changed within a Vue Computed data.
I have two sets of Variables that need to be checked,
before Computed:filteredItems should return a new list or current list.
Below are two data i'm checking
text ( the text input )
selectedInput ( currently selected item )
Current Behavior:
I've changed, selectedInput to null, this updates Computed:filteredList to be triggered. which is expected.
The first Condition is to make sure that this update returns current list if text === selectedInput.text, work as expected
However, I need a second condition to detect if text has not been changed.
<input v-model="text" />
<ul>
<li v-for="item in filteredItems" #click="text=item.text"></li>
</ul>
{
data():{
text: 1,
items: [],
tempList: [],
selectedItem: {text: 1}
},
computed: {
filteredItems(){
// when selectedItem.text === current text input, do not run
if (this.selectedItem.text === text) return this.tempList;
// how do i detect if selectedItem.text has not been changed
if (this.selectedItem.text.hasNotChange??) return this.tempList;
}
}
}
Data Flow: 1update the text > 2filter list > 3click on listItem, update (1) text
[input(text): update on type ] >
[li(filteredItem): filter list on type by value (text) and (selectedInput.text) ] >
[li(item)#click: update (1), and also another value(selectedInput.text) input(text) to equal (item.text) ]
This cycle works until I have action somewhere else that updates selectedInput.text
is there something i can do with a setter/getter for the Text model.
Create a variable, changed. Watch selectedItem.text, and set changed to true. In a watcher on text, set changed to false.
I got this to work using a temp variable
data(){
return: {
text: "",
temp: {
text
}
}
}
computed(){
filteredList(){
var temporaryList,originalList,filteredList
if ((this.text === $store.state.selectedText )||
(this.text === this.temp.text ) ) {
return temporaryList || originalList
}
// update
this.temp.text = this.text
return filteredList
}
}
thought it would be a bad practice to update variables within a Computed method.
I am using YUI Paginator API for pagination and I need to show Total number of pages on screen. I saw that there is a function getTotalPages() in API but I am unsure about how to use it, there isn't enough documentation. Also after looking at some other documentation I tried using {totalPages} but didn't work.
Can somebody help me out in this issue? Thanks in advance!!
Below is the code snippet I am using. Please refer to template object from config:
config = {
rowsPerPage: 100,
template :
'<p class="klass">' +
'<label>Total pages: {totalPages}</label>'+
'<label>Page size: {RowsPerPageDropdown}</label>'+
'</p>',
rowsPerPageDropdownClass : "yui-pg-rpp-options",
rowsPerPageOptions : [
{ value : 100 , text : "100" },
{ value : 250 , text : "250" },
{ value : 500 , text : "500" },
{ value : 1000 , text : "1000" },
{ value : tstMap[tabName].length , text : "All" }
],
};
var myPaginator = new YAHOO.widget.Paginator(config);
The Paginator utility allows you to display an item or a group of items depending on the number of items you wish to display at one time.
Paginator's primary functionality is contained in paginator-core and is mixed into paginator to allow paginator to have extra functionality added to it while leaving the core functionality untouched. This allows paginator-core to remain available for use later on or used in isolation if it is the only piece you need.
Due to the vast number of interfaces a paginator could possibly consist of, Paginator does not contain any ready to use UIs. However, Paginator is ready to be used in any Based-based, module such as a Widget, by extending your desired class and mixing in Paginator. This is displayed in the following example:
YUI().use('paginator-url', 'widget', function (Y){
var MyPaginator = Y.Base.create('my-paginator', Y.Widget, [Y.Paginator], {
renderUI: function () {
var numbers = '',
i, numberOfPages = this.get('totalPages');
for (i = 1; i <= numberOfPages; i++) {
// use paginator-url's formatUrl method
numbers += '' + i + '';
}
this.get('boundingBox').append(numbers);
},
bindUI: function () {
this.get('boundingBox').delegate('click', function (e) {
// let's not go to the page, just update internally
e.preventDefault();
this.set('page', parseInt(e.currentTarget.getContent(), 10));
}, 'a', this);
this.after('pageChange', function (e) {
// mark the link selected when it's the page being displayed
var bb = this.get('boundingBox'),
activeClass = 'selected';
bb.all('a').removeClass(activeClass).item(e.newVal).addClass(activeClass);
});
}
});
var myPg = new MyPaginator({
totalItems: 100,
pageUrl: '?pg={page}'
});
myPg.render();
});
This is my first question ever thus I apologize in advance might I use the wrong netiquette.
I'm exploring different solutions to implement a two way binding using Javascript only, I ran across the 'common mistake' when using a Closure inside a for loop hence having the counter variable always set on the last item, I found the explanation (and the solution) on this very site but then I came across a different issue I'd appreciate some help for.
Imagine we have two sets of data, where one contains proper data, i.e.:
var data = {
data1 : 0
};
The other a collection of objects describing 3 elements :
var elements = {
1 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
2 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
3 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
}
}
See the complete codesnippet below
var data = {
data1 : 0
};
var elements = {
1 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
2 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
3 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
}
}
// This is our main object, we define the properties only ...
var _elem = function (props,id){
this.id = id;
this.target = document.getElementById(props.target);
this.element = document.createElement(props.element);
this.events = props.events;
this.value = props.value;
}
// Then we add a method to render it on the page ...
_elem.prototype.render = function(){
// I added the Object Id for debugging purposes
this.element.innerHTML = this.value.data1 + ' ['+this.id+']';
this.target.appendChild(this.element);
}
// ... and another to change the underlying data and re - render it
_elem.prototype.add = function(){
// Since the data is a reference to the same data object
// We expect to change the value for all the elements
this.value.data1++;
this.render();
}
// First we looop trough the array with the element definition and
// Cast each item into a new element
for(var el in elements){
elements[el] = new _elem(elements[el],el);
}
// Then we apply the event listener (if any event description is present)
for(var el in elements){
if(!elements[el].hasOwnProperty( 'events' )){
continue;
}
// We use the anonymous function here to avoid the "common mistake"
(function() {
var obj = elements[el];
var events = obj.events;
for(var ev in events){
obj.element.addEventListener(ev,function(){ obj[events[ev]]() });
}
})();
}
// And finally we render all the elements on the page
for(var el in elements){
elements[el].render(elements[el]);
}
div {
padding: 10px;
border: solid 1px black;
margin: 5px;
display: inline-block;
}
<html>
<head>
</head>
<body>
<div id="main"></div>
</body>
</html>
Now, if we click button [1] it will update itself and the following, resulting in this sequence:
0 [2] 0 [3] 1 [1]
We refresh the page and this time click button [2], the sequence will be:
0 [1] 0 [3] 1 [2]
Button [3] Instead will update itself only
0 [1] 0 [2] 1 [3]
I did look for this topic before posting but all I could find were questions similar to this: addEventListener using for loop and passing values , where the issue was the counter variable holding always the last value
In this case instead it seems the issue to be the opposite, or the object holding the initial value and the ones following (if you keep clicking you will see what I mean)
What am I do wrong?
The issue appears to be that you are re-appending your child elements on each "refresh", which shifts the order of the elements and gives the illusion of refreshing multiple elements.
You need to differentiate between an initial render and subsequent refreshes.
I recommend that you remove the append from your render function and instead handle appending in your final for loop:
// And finally we render all the elements on the page
for(el in elements){
elements[el].render(elements[el]);
elements[el].target.append(elements[el].element);
}
Note that there are multiple "issues" with your code, including global variables in several locations. And I'm not confident that your architecture will scale well. But, those issues are outside the scope of your question, and you'll learn as you go... no reason to expect that everyone will know everything, and you may find that your current solution works just fine for what you need it to do.
var data = {
data1 : 0
};
var elements = {
1 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
2 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
3 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
}
}
// This is our main object, we define the properties only ...
var _elem = function (props,id){
this.id = id;
this.target = document.getElementById(props.target);
this.element = document.createElement(props.element);
this.events = props.events;
this.value = props.value;
}
// Then we add a method to render it on the page ...
_elem.prototype.render = function(){
// I added the Object Id for debugging purposes
this.element.innerHTML = this.value.data1 + ' ['+this.id+']';
}
// ... and another to change the underlying data and re - render it
_elem.prototype.add = function(){
// Since the data is a reference to the same data object
// We expect to change the value for all the elements
this.value.data1++;
this.render();
}
// First we looop trough the array with the element definition and
// Cast each item into a new element
for(var el in elements){
elements[el] = new _elem(elements[el],el);
}
// Then we apply the event listener (if any event description is present)
for(var el in elements){
if(!elements[el].hasOwnProperty( 'events' )){
continue;
}
// We use the anonymous function here to avoid the "common mistake"
(function() {
var obj = elements[el];
var events = obj.events;
for(ev in events){
obj.element.addEventListener(ev,function(){ obj[events[ev]]() });
}
})();
}
// And finally we render all the elements on the page
for(var el in elements){
elements[el].render(elements[el]);
elements[el].target.appendChild(elements[el].element);
}
div {
padding: 10px;
border: solid 1px black;
margin: 5px;
display: inline-block;
}
<html>
<head>
</head>
<body>
<div id="main"></div>
</body>
</html>
I have a view that has a tooltip attribute. I want to set that attribute dynamically on initialize or render. However, when I set it, it appears on the next instantiation of that view instead of the current one:
var WorkoutSectionSlide = Parse.View.extend( {
tag : 'div',
className : 'sectionPreview',
attributes : {},
template : _.template(workoutSectionPreviewElement),
initialize : function() {
// this.setDetailsTooltip(); // doesn't work if run here either
},
setDetailsTooltip : function() {
// build details
...
// set tooltip
this.attributes['tooltip'] = details.join(', ');
},
render: function() {
this.setDetailsTooltip(); // applies to next WorkoutViewSlide
// build firstExercises images
var firstExercisesHTML = '';
for(key in this.model.workoutExerciseList.models) {
// stop after 3
if(key == 3)
break;
else
firstExercisesHTML += '<img src="' +
(this.model.workoutExerciseList.models[key].get("finalThumbnail") ?
this.model.workoutExerciseList.models[key].get("finalThumbnail").url : Exercise.SRC_NOIMAGE) + '" />';
}
// render the section slide
$(this.el).html(this.template({
workoutSection : this.model,
firstExercisesHTML : firstExercisesHTML,
WorkoutSection : WorkoutSection,
Exercise : Exercise
}));
return this;
}
});
Here is how I initialize the view:
// section preview
$('#sectionPreviews').append(
(new WorkoutSectionPreview({
model: that.workoutSections[that._renderWorkoutSectionIndex]
})).render().el
);
How can I dynamically set my attribute (tooltip) on the current view, and why is it affecting the next view?
Thanks
You can define attribute property as a function that returns object as result. So you're able to set your attributes dynamically.
var MyView = Backbone.View.extend({
model: MyModel,
tagName: 'article',
className: 'someClass',
attributes: function(){
return {
id: 'model-'+this.model.id,
someAttr: Math.random()
}
}
})
I hope it hepls.
I think your problem is right here:
var WorkoutSectionSlide = Parse.View.extend( {
tag : 'div',
className : 'sectionPreview',
attributes : {} // <----------------- This doesn't do what you think it does
Everything that you put in the .extend({...}) ends up in WorkoutSectionSlide.prototype, they aren't copied to the instances, they're shared by all instances through the prototype. The result in your case is that you have one attributes object that is shared by all WorkoutSectionSlides.
Furthermore, the view's attributes are only used while the the object is being constructed:
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
The _ensureElement call is the thing that uses attributes and you'll notice that it comes before initialize is called. That order combined with the prototype behavior is why your attribute shows up on the next instance of the view. The attributes is really meant for static properties, your this.$el.attr('tooltip', ...) solution is a good way to handle a dynamic attribute.