I setup a list of objects, add them to content[] array list. So far so fine. Ember DOM in App.list show correct data. Now, when I start altering the content properties without remove/add/replaceAt() any object in App.list it seems Ember doesn't pick this up.
View:
{{#each item in App.list.content}}
{{item.title}}
{{/each}}
Code:
function myApp() {
App = Ember.Application.create({});
App.Item = Ember.Object.extend({
title: null,
parent: null
});
App.MyList = Ember.Object.extend({
title: null,
content: [],
changed: function() {
console.log('here');
}.observes('content')
});
App.list = App.MyList.create({
title: "foobar",
content: [
App.Item.create({
title: "item1"
}),
App.Item.create({
title: "item2"
})
]
});
console.log(App.list.content);
// Add 3rd object to list
App.list.get('content').pushObject(
App.Item.create({
title: "item3"
})
);
}
..and later in some random Ember ArrayController I do this:
for (var i = 0; i < 2; i++) {
App.list.content[i].title = "I CHANGED YOU";
}
Looking at my App.list the content is correct, but view is not. Am I missing something? If I have to guess ArrayHasChanged() seems to be rigged for array size changed or object being changed which I'm not doing, I'm altering property data within specific objects of the content[] array. Is it possible or do I have to removeAt()/Add/Delete objects?
You need you use get and set so the observers/bindings trigger with your changes.
// Bad
App.list.content[i].title = "blah";
// Good
App.get('list').objectAt(i).set('title', 'blah');
If this still does not work for you there might be something else missing. If you could post a jsfiddle that would help a lot!
Related
I need a default value for a field that I get from a datasource, and bind to that field using an observable. (That value can then be updated if needed by the user using a treeview). I can read the initial remote datasource, build the observable and bind the value to the field. I can then pop up a dialog, show a tree and return the values. What I cant seem to do is set the value of the observable because it is based on a datasource, and therefore seems to be a much bigger and more complicated json object which I am viewing in the console. I have also had to bind differently in order to get that working as well as shown below.
Below if just a snippet, but should give an idea. The remote data source returns just: {"name":"a name string"}
<p>Your default location is currently set to: <span id="repName" data-bind="text: dataSource.data()[0].name"></span></p>
<script>
$(document).ready(function () {
var personSource2 = new kendo.data.DataSource({
schema: {
model: {
fields: {name: { type: "string" }}
}
},
transport: {
read: {
url: "https://my-domain/path/paultest.reportSettings",
dataType: "json"
}
}
});
personSource2.fetch(function(){
var data = personSource2.data();
console.log(data.length); // displays "1"
console.log(data[0].name); // displays "a name string"
var personViewModel2 = kendo.observable({
dataSource: personSource2
});
var json = personViewModel2.toJSON();
console.log(JSON.stringify(json));
observName1 = personViewModel2.get("dataSource.data.name");
console.log("read observable: "+observName1);
kendo.bind($(''#repName''), personViewModel2);
});
After a lot of playing around, I managed to get the value to bind using:
data-bind="text: dataSource.data()[0].name"
but I can't find this documented anywhere.
Where I output the observable to the console, I get a great big object, not the simple observable data structure I was expecting. I suspect I am missing something fundamental here!
I am currently just trying to read the observable above, but can't get it to return the string from the json source.
personSource2.fetch(function(){
var data = personSource2.data();
console.log(data.length); // displays "1"
console.log(data[0].name); // displays "Jane Doe"
var personViewModel2 = kendo.observable({
dataSource: personSource2
});
var json = personViewModel2.toJSON();
console.log(JSON.stringify(json));
observName1 = personViewModel2.get("dataSource.data()[0].name");
console.log("read observable: "+observName1);
personViewModel2.set("dataSource.data()[0].name","Another Value");
observName1 = personViewModel2.get("dataSource.data()[0].name");
console.log("read observable: "+observName1);
kendo.bind($(''#repName''), personViewModel2);
});
I've tested in various ways... Still, It isn't working.
I don't seem to doing anything wrong
exactly same code as reselect doc
redux store is all normalized
reducers are all immutable
From parent component, I just pass down a prop with id and from child component, connected with redux and used selector to get that exact item by id(from parent component)
### This is what Parent components render looks like
render() {
return (
<div>
<h4>Parent component</h4>
{this.props.sessionWindow.tabs.map(tabId =>
<ChildComponentHere key={tabId} tabId={tabId} />
)}
</div>
);
}
### This is what Child component looks like
render() {
const { sessionTab } = this.props (this props is from connect() )
<div>
<Tab key={sessionTab.id} tab={sessionTab} />
</div>
))
}
### Selectors for across multiple components
const getTheTab = (state: any, ownProps: IOwnProps) => state.sessionWindows.sessionTab[ownProps.tabId];
const makeTheTabSelector = () =>
createSelector(
[getTheTab],
(tab: object) => tab
)
export const makeMapState = () => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props)
}
}
return mapStateToProps
}
Weirdly Working solution: just change to deep equality check.(from anywhere)
use selectors with deep equality works as expected.
at shouldComponentUpdate. use _.isEqual also worked.
.
1. const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
)
2. if (!_isEqual(this.props, nextProps) || !_isEqual(this.state, nextState)){return true}
From my understanding, my redux is always immutable so when something changed It makes new reference(object or array) that's why react re-renders. But when there is 100 items and only 1 item changed, only component with that changed props get to re-render.
To make this happen, I pass down only id(just string. shallow equality(===) works right?)using this id, get exact item.(most of the components get same valued input but few component get different valued input) Use reselect to memoize the value. when something updated and each component get new referenced input compare with memoized value and re-render when something trully changed.
This is mostly what I can think of right now... If I have to use _isEqual anyway, why would use reselect?? I'm pretty sure I'm missing something here. can anyone help?
For more clarification.(hopefully..)
First,My redux data structure is like this
sessionWindow: {
byId: { // window datas byId
"windowId_111": {
id: "windowId_111",
incognito: false,
tabs: [1,7,3,8,45,468,35,124] // this is for the order of sessionTab datas that this window Item has
},
"windowId_222": {
id: "windowId_222",
incognito: true,
tabs: [2, 8, 333, 111]
},{
... keep same data structure as above
}
},
allIds: ["windowId_222", "windowId_111"] // this is for the order of sessionWindow datas
}
sessionTab: { // I put all tab datas here. each sessionTab doesn't know which sessionWindow they are belong to
"1": {
id: 1
title: "google",
url: "www.google.com",
active: false,
...more properties
},
"7": {
id: 7
title: "github",
url: "www.github.com",
active: true
},{
...keep same data structure as above
}
}
Problems.
1. when a small portion of data changed, It re-renders all other components.
Let's say sessionTab with id 7's url and title changed. At my sessionTab Reducer with 'SessionTabUpdated" action dispatched. This is the reducer logic
const updateSessionTab = (state, action) => {
return {
...state,
[action.tabId]: {
...state[action.tabId],
title: action.payload.title,
url: action.payload.url
}
}
}
Nothing is broken. just using basic reselect doesn't prevent from other components to be re-rendered. I have to use deep equality version to stop re-render the component with no data changed
After few days I've struggled, I started to think that the problem is maybe from my redux data structure? because even if I change one item from sessionTab, It will always make new reference like {...state, [changedTab'id]: {....}} In the end, I don't know...
Three aspects of your selector definition and usage look a little odd:
getTheTab is digging down through multiple levels at once
makeTheTabSelector has an "output selector" that just returns the value it was given, which means it's the same as getTheTab
In mapState, you're passing the entire props object to theTabSelector(state, props).
I'd suggest trying this, and see what happens:
const selectSessionWindows = state => state.sessionWindows;
const selectSessionTabs = createSelector(
[selectSessionWindows],
sessionWindows => sessionWindows.sessionTab
);
const makeTheTabSelector = () => {
const selectTabById = createSelector(
[selectSessionTabs, (state, tabId) => tabId],
(sessionTabs, tabId) => sessionTabs[tabId]
);
return selectTabById;
}
export const makeMapState() => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props.tabId)
}
}
return mapStateToProps
}
No guarantees that will fix things, but it's worth a shot.
You might also want to try using some devtool utilities that will tell you why a component is re-rendering. I have links to several such tools in the Devtools#Component Update Monitoring section of my Redux addons catalog.
Hopefully that will let you figure things out. Either way, leave a comment and let me know.
I am new to cyclejs and rxjs in general and was hoping someone could help me solve my problem.
I am trying to build a demo application for my understanding and stuck with rendering JSON objects on the DOM.
My demo application calls the NASA near earth objects API for the past 7 days and tries to display them.
There is a Load More button at the bottom which on clicking will load data of the previous 7 days (Today - 7 upto Today - 14).
The response I get from the API is as follows
{
"links" : {
"next" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2016-09-06&end_date=2016-09-12&detailed=false&api_key=DEMO_KEY",
"prev" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2016-08-25&end_date=2016-08-31&detailed=false&api_key=DEMO_KEY",
"self" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2016-08-31&end_date=2016-09-06&detailed=false&api_key=DEMO_KEY"
},
"element_count" : 39,
"near_earth_objects" : {
"2016-09-06" : [{
some data
},
{
some data
}],
2016-08-31: [{...}],
...
}
}
I am interested in near_earth_objects JSON object but I am unable to map it beacause of it being an Object.
How do I handle such a situations? Below is the code that I have
function main(sources) {
const api_key = "DEMO_KEY";
const clickEvent$ = sources.DOM.select('.load-more').events('click');
const request$ = clickEvent$.map(() => {
return {
url: "https://api.nasa.gov/neo/rest/v1/feed?start_date=2015-09-06&end_date=2016-09-13&api_key=" + api_key,
method: "GET"
}
}).startWith({
url: "https://api.nasa.gov/neo/rest/v1/feed?start_date=2016-08-31&end_date=2016-09-06&api_key=" + api_key,
method: "GET"
});
const response$$ = sources.HTTP.filter(x$ => x$.url.indexOf("https://api.nasa.gov/neo/rest/v1/feed") != -1).select(response$$);
const response$ = response$$.switch(); //flatten the stream
const nasa$ = response$.map(response => {
return response.body
});
const sinks = {
DOM: nasa$.map(nasa =>
([nasa.near_earth_objects]).map(objects => {
var vdom = [];
//I am not very happy with this part. Can this be improved?
for (var key in objects) {
if (objects.hasOwnProperty(key)) {
vdom.push(objects[key].map(obj => div([
h1(obj.name)
])))
}
}
//returning the vdom does not render on the browser. vdom is an array of arrays. How should i correct this?
console.log(vdom);
return vdom;
})
),
HTTP: request$
};
return sinks;
};
Conceptually, you want to extract the entries of nasa.near_earth_objects (i.e., turn the Object into an Array), then flat map that Array into an Observable sequence.
I'll assume you're already using lodash in your project (you can do it without lodash, but you'll just need to write more glue code manually). I'll also assume you're importing RxJS' Observable as Rx.Observable; adjust the names below to suite your code.
You can accomplish the first task using _.toPairs(nasa.near_earth_objects), and the second part by calling .flatMap(), and returning Rx.Observable.from(near_objects). The resulting Observable will emit items for each key in nasa.near_earth_objects. Each item will be an array, with item[0] being the item's key (e.g., 2016-09-06) and item[1] being the item's value.
Using that idea, you can replace your DOM sink with something like:
nasa$.map(nasa => _.toPairs(nasa.near_earth_objects))
.flatMap(near_objects => Rx.Observable.from(near_objects))
.map(near_object => div([
h1(near_object[1].name)
]))
),
I am trying to understand how a collection passed in to a view is being referenced and used. There does not seem to be any reference to the collection, but it's models are being used. Also, I'm unable to get the collection items to be bound/displayed when I use my collection that is bound to my api, but it works when I use the hard coded collection. Is it because I need to fetch my collection at some point? My collection is fine and the path is fine. I use it throughout my app without any problems.
Below is the code:
module.exports = Backbone.Collection.extend({
model: Employee,
url:"api/employees"
});
MainView
module.exports = base.extend({
el: '#content',
template:template,
initialize: function () {
// var items = new EmployeeCollection([
// {id: 1, firstName: "Test1 fName", lastName: "Test1 lName"},
// {id: 2, firstName: "Test2 fName", lastName: "Test2 lName"},
// {id: 3, firstName: "Test3 fName", lastName: "Test3 lName"}
// ]);
EmployeeListCollection = new EmployeeCollection();
//this.itemView = new EmployeeListView({collection: items}); //this syntax works if I uncomment the code above to create my item list
this.itemView = new EmployeeListView({collection: EmployeeListCollection}); //do i need to fetch the collection at some point?
this.render();
},
render: function(){
this.el.innerHTML = Mustache.to_html(this.template);
this.itemView.render();
$("#empList").html(this.itemView.el);
}
});
ItemListView - where does the passed in collection get referenced? I see a model reference, but I passed in a collection.
module.exports = base.extend({
//tagName:'ul',
tagName:'select',
initialize: function(){
_.bindAll(this, "renderItem");
},
renderItem: function(model){
console.log('employeelistloop render');
this.itemView = new EmployeeListItemView({model: model});
this.itemView.render();
$(this.el).append(this.itemView.el);
},
render: function(){
this.collection.each(this.renderItem);
},
});
Actually, I think I figured what the problem was. I did indeed need to fetch the collection in my ItemListView. And I also realize now that render is being called before renderitem and that each model in the collection is being passed in to the renderitem function. I was able to get my collection to work by calling fetch in my render:
var self = this;
this.collection.fetch().done(function(){
self.collection.each(self.renderItem);
});
So it all makes sense now. But I still don't fully understand why my code runs twice. When I do a console.log in the initialize and render of MainView, I get two calls every time the first time I run it.
Looking at the docs you can pass startup data to a widget:
editor.execCommand( 'simplebox', {
startupData: {
align: 'left'
}
} );
However this data is pointless as there seems to be no way to affect the template output - it has already been generated before the widget's init, and also the data isn't even available at that point:
editor.widgets.add('uselesswidget', {
init: function() {
// `this.data` is empty..
// `this.dataReady` is false..
// Modifying `this.template` here does nothing..
// this.template = new CKEDITOR.template('<div>new content</div>');
// Just after init setData is called..
this.on('data', function(e) {
// `e.data` has the data but it's too late to affect the tpl..
});
},
template: '<div>there seems to be no good way of creating the widget based on the data..</div>'
});
Also adding a CKEditor tag throws a "Uncaught TypeError: Cannot read property 'align' of undefined" exception so it seems the data is also not passed to the original template:
template: '<div>Align: {align}</div>'
What is the point of having a CKEDITOR.template.output function which can accept a context, if there's no way of dynamically passing data?
The only horribly hacky solution I've found so far is to intercept the command in a beforeCommandExec and block it, then modify the template and manually execute the command again..
Any ideas to generate dynamic templates based on passed data? Thanks.
Here's how I did it.
Widget definition:
template:
'<div class="myEditable"></div>',
init: function () {
// Wait until widget fires data event
this.on('data', function(e) {
if (this.data.content) {
// Clear previous values and set initial content
this.element.setHtml('')
var newElement = CKEDITOR.dom.element.createFromHtml( this.data.content );
this.element.append(newElement,true);
this.element.setAttribute('id', this.data.id);
}
// Create nestedEditable
this.initEditable('myEditable', {
selector: '.myEditable',
allowedContent: this.data.allowedContent
})
});
}
Dynamic widget creation:
editor.execCommand('myEditable', {startupData: {
id: "1",
content: "some <em>text</em>",
allowedContent: {
'em ul li': true,
}
}});