Right so I have a dilemma, that seems like a simple question but I can't figure it out.
I have a nested array:
$scope.rootItem = {
id: '1',
type: 'course',
title: 'Adobe Photoshop CC for beginners',
items: [{
id: '2',
type: 'label',
title:'Label Title',
items:[{
id: '3',
type: 'module',
title:'Module title',
items: [{
id: '4',
type: 'topic',
title:'Topic title',
items: [{
id: '5',
type: 'content',
title:'Content title'
}, {
id: '6',
type: 'content',
title:'Content title'
}]
}]
},{
id: '7',
type: 'resources',
title:'Resources'
},{
id: '8',
type: 'module',
title:'Module title',
items: [{
id: '9',
type: 'topic',
title:'Topic',
items: [{
id: '10',
type: 'question',
title:'Question title'
}]
}, {
id: '11',
type: 'topic',
title:'Topic title',
items: [{
id: '12',
type: 'content',
title:'Content title'
}]
}]
}]
},{
id: '14',
type: 'assessmentLabel',
title: 'Assessment Label',
items: [{
id: '15',
type: 'assessment',
title: 'Assessment Title',
items: [{
id: '16',
type: 'courseAssessment',
title: 'Course Assessment Question',
items: []
}]
}]
}]
};
That is outputted using ng-repeat. All works great there, by the way it is also sortable using ng-sortable (based on JQuery UI Sortable).
What I'm trying to do is duplicate lets say id: 5 using angular.copy().
HTML:
<a href="" title="Duplicate Content" data-ng-click="duplicate(ngModelItem, $parent.ngModelItem.items)">
<span class="icon-duplicate"></span>
</a>
That seems to work fine too. I'm able to pass the object to the function.
The problem arises when I try and push that object to its parents array. I read about $parent and what I think would make sense is passing $parent.ngModelItems.items to the ng-click as such:
data-ng-click="duplicate(ngModelItem, $parent.ngModelItem.items)"
Which to me makes sense, pass parents ngModelItem.items (items is array that ID:5 is part of). But I can't figure out why do I get $parent.ngModelItem.items as undefined.
This is my controller:
$scope.duplicate = function(item, parent) {
var itemCopy = angular.copy(item);
parent.push(item);
};
HTML ng-repeat:
<ul class="apps-container" ui-sortable="sortableOptions" ng-model="ngModelItem.items" ng-class="ngModelItem.type">
<li class="innerCont" ng-repeat="innerItem in ngModelItem.items">
<tg-dynamic-directive ng-model="innerItem" tg-dynamic-directive-view="getView">
</tg-dynamic-directive>
</li>
</ul>
But angular seems to have different ideas. So I guess my question is how can I pass parents ngModelItem.items (rootItem.items) so that I can access that array?
Can someone please explain why {{$parent.$parent.ngModelItems.id}} returns correct parent id. Yet when I try to pass that parent to the function such as
data-ng-click="duplicate(parent.parent.ngModelItem.items)"
It doesnt work
Directive below:
angular.module('tg.dynamicDirective', [])
.directive('tgDynamicDirective', ['$compile',
function($compile) {
'use strict';
function templateUrlProvider(getView, ngModelItem) {
if (getView) {
if (typeof getView === 'function') {
var templateUrl = getView(ngModelItem) || '';
if (templateUrl) {
return templateUrl;
}
} else if (typeof getView === 'string' && getView.length) {
return getView;
}
}
return '';
}
return {
restrict: 'E',
require: '^ngModel',
scope: true,
template: '<div ng-include="templateUrl"></div>',
link: function(scope, element, attrs, ngModel) {
scope.$watch(function() {
var ngModelItem = scope.$eval(attrs.ngModel);
var getView = scope.$eval(attrs.tgDynamicDirectiveView);
scope.ngModelItem = ngModelItem;
return templateUrlProvider(getView, ngModelItem);
}, function(newValue, oldValue) {
scope.templateUrl = newValue;
});
}
};
}
]);
After few hours of trying to fix this, and reading numerous articles about $scope inheritance I found out that ng-if create new scope using prototypical inheritance. Which I was not accounting for.
Which required me to insert one more $parent when passing it to the function as such:
data-ng-click="duplicate(ngModelItem, $parent.$parent.$parent.ngModelItem)"
and then in the controller do something like this:
$scope.duplicate = function(item, parent) {
var itemCopy = angular.copy(item);
var parentArray = parent.items;
parentArray.push(itemCopy)
};
Hope this will save someone hours of work, whoever runs into this problem.
Related
I'm working on an inventory app with Vuejs for this project, but my problem is specifically a JS related one.
In my data I have the user data which is the logged userData which is taken from a form and then the static categories and locations which you can choose from below that.
I am trying to pull out the unique categories and how many times the appear in a new array of objects that would look like :
[{title: 'Books', amount: 3 }, {title: 'Recods', amount: 1 }, ...]
I believe what I have is a scope issue.
Data
userData: {
items: [
{
itemName: 'test book',
category: 'Books',
location: 'Kitchen',
},
{
itemName: 'test book 2',
category: 'Books',
location: 'Garage',
},
{
itemName: 'test book 3',
category: 'Books',
location: 'Basement',
},
{
itemName: 'test record',
category: 'Records',
location: 'Basement',
},
{
itemName: 'test furniture',
category: 'Furniture',
location: 'Garage',
},
],
categories: ['Books', 'Movies', 'Records', 'Furniture'],
locations: ['Basement', 'Garage', 'Kitchen'],
},
I'm trying to get this to work like I have here with 'Books', but for all categories.
This is what I have displaying with the code below. It reads 'Items: 3' because I have 3 Books in my userData, but I need it to display the amount for each unique category.
I cannot figure out what to place in the code below
filteredItems = items.filter((item) => item.category === 'Books').length
method/function
convertedCategories() {
const items = this.userData.items
const filteredItems = items.filter((item) => item.category === 'Books').length
function getItemCountOfCategory(categoryName) {
return filteredItems
}
function convertCategoriesIntoCards(categories) {
return categories.map((el) => {
return {
title: el,
amount: getItemCountOfCategory(el),
}
})
}
return convertCategoriesIntoCards(this.userData.categories)
},
I apologize if I haven't broken this down clear enough; it's still very hard for me to extract a particular line that pertains to the question out of so much code.
If anything is unclear, please let me know!
Something like this should work.
convertedCategories() {
const items = this.userData.items
function getItemCountOfCategory(categoryName) {
return items.filter((item) => item.category === categoryName).length
}
function convertCategoriesIntoCards(categories) {
return categories.map((el) => {
return {
title: el,
amount: getItemCountOfCategory(el),
}
})
}
return convertCategoriesIntoCards(this.userData.categories)
},
Basically i've made proyxy-component which renders different components based on what the :type is and it works great. The point is that I create a schema of the form controls and a separate data object where the data from the form controls is stored. Everything is working good but i have a problem when formData object contains nested objects.
In my example test.test1
How can i make the v-model value dynamic which is generated based on what the string is.
My Compoennt
<proxy-component
v-for="(scheme, index) in personSchema.list"
:key="index"
:type="scheme.type"
:props="scheme.props"
v-model="formData[personSchema.prefix][scheme.model]"
v-validate="'required'"
data-vv-value-path="innerValue"
:data-vv-name="scheme.model"
:error-txt="errors.first(scheme.model)"
></proxy-component>
Data
data() {
return {
selectOptions,
formData: {
person: {
given_names: '',
surname: '',
sex: '',
title: '',
date_of_birth: '',
place_of_birth: '',
nationality: '',
country_of_residence: '',
acting_as: '',
test: {
test1: 'test',
},
},
},
personSchema: {
prefix: 'person',
list: [
{
model: 'given_names',
type: 'custom-input-component',
props: {
title: 'Name',
},
},
{
model: 'surname',
type: 'custom-input-componentt',
props: {
title: 'Surname',
},
},
{
model: 'test.test1',
type: 'custom-input-component',
props: {
title: 'test 1',
},
},
{
model: 'sex',
type: 'custom-select-component',
props: {
title: 'Sex',
options: selectOptions.SEX_TYPES,
trackBy: 'value',
label: 'name',
},
},
],
},
};
},
I would recomment you to write a vue-method (under the data section) that returns the object for v-model
v-model="resolveObject(formData[personSchema.prefix][scheme.model])"
or
v-model="resolveObject([personSchema.prefix] , [scheme.model])"
There you can do handle the dot-notation and return the proper nested property.
I don't think it's possible directly with v-model, you can take a look at:
https://v2.vuejs.org/v2/guide/reactivity.html
Maybe the best solution would be use a watch (deep: true) as a workaround.
(I would try first to use watch properties inside formData[personSchema.prefix][scheme.model].)
I am very much beginner to Sencha Touch. I want to display a static list to the screen. The code I tried is from sencha docs guides 'using list'. However, blank screen appears when compiled and run. Do I have to add the list to the Viewport? What am I missing? Please help.
My code is:
Ext.application({
launch: function () {
Ext.create('Ext.List', {
store: {
fields: ['name'],
data: [
{ name: 'A' },
{ name: 'B' },
{ name: 'C' },
{ name: 'D' }
]
},
itemTpl: '{name}'
});
}
});
May be this one help you
Ext.create('Ext.List', {
fullscreen: true,
itemTpl: '{title}',
data: [
{ title: 'Item 1' },
{ title: 'Item 2' },
{ title: 'Item 3' },
{ title: 'Item 4' }
]
});
Sorry! My bad. I made it with a little bit of work.
I just did this:
launch: function() {
var list = Ext.Create('Ext.List',{..
.....
.....
itemTpl = '{name}'
});
Ext.Viewport.add(list);
}
Here is the test data:
[{
id: 1,
isActive: true,
documentIdentifier: '00012345',
sourceSiteName: 'Aviation Industry Ltd.',
targetSiteName: 'VendorName',
createDate: '2013-03-06T14:12:03.2341054+02:00',
archiveEvent: 'Rejected',
archive: 'PurchaseOrder',
previousWhatsNewEvents: [{
id: 2,
isActive: true,
documentIdentifier: '00012345',
sourceSiteName: 'Aviation Industry Ltd.',
targetSiteName: 'Vendor Name',
createDate: '2013-03-06T14:12:03.2341054+02:00',
archiveEvent: 'Approved',
archive: 'PurchaseOrder',
isPin: true,
IsDocumentReadByMe: false,
IsDocumentReadByOthers: true,
documentYear: 2013,
businessDirection: 1
}],
isPin:true,
IsDocumentReadByMe:false,
IsDocumentReadByOthers:true,
documentYear:2013,
businessDirection:1
}
Here is the template:
tpl: [
'<div class="n-row-title">',
'<div class="n-doc-status n-doc-status-{archiveEvent:this.toLower} n-float-left"> </div>',
'<span class="n-hmargin-10">{archiveEvent}</span>',
'</div>',
'<div class="n-clear"></div>',
'<div class="n-row-sub-title">{createDate:date("m/d/Y H:i")}</div>',
'<div class="n-whatsnew-previous-events">',
'<tpl for="previousWhatsNewEvents">',
'<div class="n-row-title">',
'<div class="n-doc-status n-doc-status-{archiveEvent:this.toLower} n-float-left"> </div>',
'<span class="n-hmargin-10">{archiveEvent}</span>',
'</div>',
'<div class="n-clear"></div>',
'<div class="n-row-sub-title">{createDate:date("m/d/Y H:i")}</div>',
'</tpl>',
'</div>',
{
toLower: function (value) {
return value.toLowerCase();
}
}
]
Here is how Chrome is rendering the template:
Here is how IE8 is rendering it:
Any one know of a workaround?
UPDATE
Here is my module :
Ext.define('XX.model.WhatsNew', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id', type: 'int' },
{ name: 'isActive', type: 'boolean' },
{ name: 'documentIdentifier', type: 'string' },
{ name: 'sourceSiteName', type: 'string' },
{ name: 'targetSiteName', type: 'string' },
{ name: 'createDate', type: 'date', dateFormat: 'c' },
{ name: 'archiveEvent', type: 'string' },
{ name: 'archive', type: 'string' },
{ name: 'previousWhatsNewEvents' },
{ name: 'isPin', type: 'boolean' },
{ name: 'IsDocumentReadByMe', type: 'boolean' },
{ name: 'IsDocumentReadByOthers', type: 'boolean' },
{ name: 'documentYear', type: 'int' },
{ name: 'businessDirection', type:'int'}
],
hasMany: {
model: 'auxClasses.notifications.WhatsNew',
name: 'previousWhatsNewEvents'
},
proxy: {
type: 'rest',
url: 'api/WhatsNew/'
}
});
The template could not read the date format from the inner previousWhatsNewEvents childs... that made the dates go wrong!!
Your association is overridden by the field with the same name. Even if that was not the case, you association would not load the child nodes from the data because its associationKey is not configured (see the class description for an example of what it does).
Then again, if you got there, the data view component probably don't support associations... Update: That's wrong, see bellow.
So your fast way out of this is to replace the stock date format function with your own, that casts date strings using Ext.Date.format instead of new Date over which you have no control and that may vary between browsers, as pointed out by Evan.
The trick is that you need a date input format to use Ext.Date.format. In your template, you'll know the expected format:
tpl: [
'{createDate:this.date}',
{
date: function(value) {
var readFormat = 'c',
displayFormat = 'm/d/Y H:i',
date = Ext.Date.parse(value, readFormat);
return Ext.Date.format(date, displayFormat);
}
}
]
And you can even think of replacing the Ext.util.Format.date function by adding an argument to it:
Ext.util.Format.date = function(value, outFormat, inFormat) {
var date;
if (value) {
if (value instanceof Date) {
date = value;
} else if (inFormat) {
date = Ext.Date.parse(value, inFormat);
} else {
// backward compatibility
date = new Date(value);
}
return Ext.Date.format(date, outFormat);
} else {
return '';
}
};
Update
After further investigation, it appears that data view do in fact support model associations: the view's prepareData method calls getAssociatedData on the model. So, as pointed by Even again, the cleanest solution is probably to configure your association correctly.
That means removing this field:
fields: [
...
{ name: 'previousWhatsNewEvents' },
...
]
Adding the associationKey option:
hasMany: {
model: 'auxClasses.notifications.WhatsNew',
name: 'previousWhatsNewEvents',
associationKey: 'previousWhatsNewEvents'
}
And finally you have to ensure that your data is loaded through the Reader for the nested records to be read -- simply put, that means through a Proxy. So with the load method of a store, it will work while with any other way it will probably not (for example the loadData method of the store doesn't use the proxy).
I'm new to Handlebars and while I found a workaround, I'm wondering why one registered helper works and one doesn't. The example that doesn't work is an example from the HB docs.
HTML:
<ul class="global-nav clearfix">
{{#each data}}
<li>{{text}}</li>
{{/each}}
</ul>
...
<ul class="content-nav clearfix">
{{#each data}}
<li>{{text}}</li>
{{/each}}
</ul>
Data:
var nav = [
{
name: 'global',
selector: $('.global-nav'),
data: [
{
text: 'Page 1',
href: 'page1.html'
}, {
text: 'Page 2',
href: 'page2.html'
}
],
name: 'content',
selector: $('.content-nav'),
data: [
{
text: 'Section 1',
href: '#section1'
}, {
text: 'Section 2',
href: '#section2'
}
]
}
];
Compiler:
$.each(nav, function() {
var obj = this,
src = obj.selector.html(),
template = Handlebars.compile(src),
html = template(obj.data);
obj.selector.html(html);
});
HB Helper (does not work - context is undefined):
Handlebars.registerHelper('each', function(context, options) {
var ret = "";
for(var i=0, j=context.length; i<j; i++) {
ret = ret + options.fn(context[i]);
}
return ret;
});
HB Helper (does work using this instead of context):
Handlebars.registerHelper('each', function(context, options) {
var ret = "";
for(var i=0, j=this.length; i<j; i++) {
ret = ret + options.fn(this[i]);
}
return ret;
});
Any helps is appreciated.
Before looking at the rest of the JS, can I point out that the JSON looks wrong.
Name, selector and data are overwritten the second time they occur in your JSON. If this is just because of some bits being omitted when pasting to SO, then do ignore me ;o)
But if that is the real JSON, then it needs changing before looking at any functional stuff
<script>
var nav = [
{
name: 'global',
selector: $('.global-nav'),
data: [
{
text: 'Page 1',
href: 'page1.html'
}, {
text: 'Page 2',
href: 'page2.html'
}
]
}, // this line
{ // and this line added
name: 'content',
selector: $('.content-nav'),
data: [
{
text: 'Section 1',
href: '#section1'
}, {
text: 'Section 2',
href: '#section2'
}
]
}
];
</script>