When using XML to build an repeater, like the example above, how to update the array later?
<Repeater items="{{ letras }}" id="rep">
<Repeater.itemsLayout>
<StackLayout horizontalAlignment="center" orientation="horizontal" />
</Repeater.itemsLayout>
<Repeater.itemTemplate>
<Button text="{{ l }}" cssClass="palavraPrincipal" />
</Repeater.itemTemplate>
</Repeater>
I know that there is an way to do that if I create the repeater on Javascript file, but on a pre-made XML repeater?
To make changes in the viewmodel (the letras array in your example) reflect in the UI you need to use Native Script's data binding.
Below is an example which, given the XML view in your post, will:
Require the 2 modules, observable (for observable objects) and observable-array (for arrays).
Create a viewmodel, which is the object which holds the data
Bind the viewmodel to your page's binding context.
Every 1 second, add a new item to the viewmodel (which will get reflected to the UI).
I set the binding context of the page to the viewmodel. There's no need to set the binding context on the Repeater element, as calebeaires do in his example, as the Repeater will look at the page's binding context.
However, if you want to, you can use e.g. 2 different binding contexts, 1 for the page and one for the Repeater element. Just substitute
page.bindingContext = viewModel;
for
page.getViewById('rep').bindingContext = viewModel;
Full code:
var observableModule = require("data/observable");
var observableArrayModule = require("data/observable-array");
var viewModel = new observableModule.Observable({
letras: new observableArrayModule.ObservableArray([
{ l: 'First' },
{ l: 'Second' },
{ l: 'Third' },
])
});
function pageLoaded(args) {
var page = args.object;
page.bindingContext = viewModel;
//page.getViewById('rep').bindingContext = viewModel;
// Add a new item every 1 second.
setInterval(function() {
viewModel.letras.push({
l: 'New item'
});
}, 1000);
}
exports.pageLoaded = pageLoaded;
Find out:
A. Declare and variable:
var repeater;
B. Declare an ID to the repeater:
<Repeater items="{{ items }}" id="rep">
<Repeater.itemTemplate>
<Button text="{{ $value }}" />
</Repeater.itemTemplate>
</Repeater>
C. Redeclare it on page load:
function pageLoaded(args) {
var page = args.object;
repeater = view.getViewById(page, "rep");
page.bindingContext = jogo;
}
Related
I have a store with a list of entities, and another Store with and object that include one of those entities.
I want changes in the first store to be reactively reflected on the second.
I'll provide a quick example with a list of items and a list of invoices
export type Invoice = {
id: string
customer: string
items: InvoiceItem[]
}
export type InvoiceItem = {
id: string
name: string
price: number
}
Whenever the name or price of an invoice item is updated I'd like all the related Invoices to also be updated.
I created this very simple example (repl available here) but in order for the $invoices store to be updated I have to issue a $invoices = $invoices whenever the $items store changes.
Another more elegant way to do it is to subscribe to the items store and from there update the invoices store, like this:
items.subscribe(_ => invoices.update(data => data))
<script>
import { writable } from 'svelte/store'
let item1 = { id: 'item-01', name: 'Item number 01', price: 100 }
let item2 = { id: 'item-02', name: 'Item number 02', price: 200 }
let item3 = { id: 'item-03', name: 'Item number 03', price: 300 }
let items = writable([item1, item2, item3])
let invoices = writable([
{ id: 'invoice-0', customer: 'customer1', items: [item1, item3] }
])
items.subscribe(_ => invoices.update(data => data)) // refresh invoices store whenever an item is changed
const updateItem1 = () => {
$items[0].price = $items[0].price + 10
// $invoices = $invoices // alternatively, manually tell invoices store that something changed every time I change and item!!!
}
</script>
<button on:click={updateItem1}>update item 1 price</button>
<hr />
<textarea rows="18">{JSON.stringify($invoices, null, 2)}</textarea>
<textarea rows="18">{JSON.stringify($items, null, 2)}</textarea>
Is this the best way to handle this kind of scenario?
Update: thanks to the great answers and comments I came out with this more complete example: see this repl
I added some functionality that I hope will serve as basis for similar common scenarios
This is how my store api ended up:
// items.js
items.subscribe // read only store
items.reset()
items.upsert(item) // updates the specified item, creates a new one if it doesn't exist
// invoices.js
invoices.subscribe // read only store
invoices.add(invocieId, customer, date) // adds a new invoice
invoices.addLine(invoiceId, itemId, quantity)
invoices.getInvoice(invoice) // get a derived store for that particular invoice
invoice.subscribe // read only store
invoice.addLine(itemId, quantity)
A few highlights
invoices now has a lines array, each with an item and a quantity
invoices is a derived store that calculate total for each line and for the whole invoice
implementes an upsert method in items
in order to update invoices whenever an item is modified I run items.subscribe(() => set(_invoices))
also created a derived store to get a specific invoice
The solution depends on whether or not you need items independently (one item can be part of multiple invoices) or if it can be part of the invoices. If they can be one big blob, I would create invoices as a store and provide methods to update specific invoices. The items store then would be derived from the invoices.
// invoices.ts
const _invoices = writable([]);
// public API of your invoices store
export const invoices = {
subscribe: _invoices.subscribe,
addItemToInvoice: (invoideId, item) => {...},
..
};
// derived items:
const items = derived(invoices, $invoices => flattenAllInvoiceItems($invoice));
However, if they need to be separate - or if it is easier to handle item updates that way -, then I would only store the IDs of the items in the invoice store and create a derived store which uses invoices+items to create the full invoices.
// items.ts
const _items = writable([]);
// public API of your items store
export const items = {
subscribe: _items.subscribe,
update: (item) => {...},
...
};
// invoices.ts
import { items } from './items';
const _invoices = writable([]);
// public API of your invoices store
export const invoices = {
// Assuming you never want the underlying _invoices state avialable publicly
subscribe: derived([_invoices, items], ([$invoices, $items]) => mergeItemsIntoInvoices($invoices, $items)),
addItemToInvoice: (invoideId, item) => {...},
..
};
In both cases you can use invoices and items in your Svelte components like you want, interact with a nice public API and the derived stores will ensure everything is synched.
You can use a derived store like this:
let pipe = derived([invoices, items], ([$invoices, $items]) => {
return $invoices;
})
So $pipe will return an updated invoice if the invoice was changed.
$pipe will be triggered bij both stores ($items and $invoice) but only produces a result if the invoice was changed. So $pipe will not produce a result when an item changes which is not part of the invoice.
Update. I expected no result of $pipe when $invoices does not change as is the case for a writeable store. But a derived store callback will always run if $invoices or $items changes.
So we have to check if $invoices changes and use set only if we have a change.
let cache = "";
let pipe = derived([invoices, items], ([$invoices, $items], set) => {
if (JSON.stringify($invoices) !== cache) {
cache = JSON.stringify($invoices);
set($invoices);
}
}, {})
I need some help. I'm trying to set color when group by in a sharepoint list.
I'm using the following code
SP.SOD.executeFunc("clienttemplates.js", "SPClientTemplates", function() {
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
OnPostRender: function(ctx) {
var statusColors = {
'Liberada' : '#FFF1AD',
'Cancelada' : '#FFD800',
'Créditos' : '#01DF3A'
};
var rows = ctx.ListData.Row;
for (var i=0;i<rows.length;i++)
{
var status = rows[i]["Status"];
var rowId = GenerateIIDForListItem(ctx, rows[i]);
var row = document.getElementById(rowId);
row.style.backgroundColor = statusColors[status];
}
}
});
});
Can someone give a hint?
As I can see in my local Sharepoint 2013 environment, when I create a view with a group by, Sharepoint add a CSS class named « ms-gb » on the table cell that contains the group name.
Knowing this, a solution would be to edit the page of the view.
Then you can add to the page a Script Editor Webpart.
Inside this Script Editor Webpart you can add some JavaScript code to add the background-color.
Something like this for example :
<script>
$(document).ready(function(){
var statusNames = ['Liberada', 'Cancelada', 'Créditos'];
var statusColors = {
'Liberada' : '#FFF1AD',
'Cancelada' : '#FFD800',
'Créditos' : '#01DF3A'
};
$('td.ms-gb').each(function(){
for (var x=0;x<statusNames.length;x++) {
if ($(this).text().indexOf(statusNames[x]) > -1) {
$(this).css('background-color',statusColors[statusNames[x]]);
}
}
});
});
</script>
If you try this script and you don't already have a reference to JQuery in your masterpage or page layout, you will need to add one inside the script editor webpart.
You can download a local copy of JQuery or use one hosted like :
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
Hope this help.
Using ionic, I am trying to have a use case to select from a list and return back to the original view with some value. I'ved already done most of the part except detecting it has returned to the original view and passing a value back to the original view.
Here's so far what i'ved accomplished:
button that goes to a list
<button class="button button-block button-outline button-positive" ng-click="performselectUnit()"> Select Unit
</button>
this is the trigger to go to the new view with the list
$scope.performselectUnit = function(){
console.log('performselectUnit');
$state.go('app.units');
}
the view with list when press performs an action on the selected row
<ion-item collection-repeat="unit in units" class="item item-icon-right item-icon-left" ng-click="selectUnit(unit.id)">
on selection of the row it goes back to the original view with $ionicHistory.goBack()
$scope.selectUnit = function(unit_id){
console.log('performselectUnit:' + unit_id);
$ionicHistory.goBack();
}
From the last function, how do detect its gone back to the original view and pass some value.
Thanks.
UPDATE:
I tried this.
Broadcast the result
$scope.selectUnit = function(unit_id){
console.log('performselectUnit:' + unit_id);
$ionicHistory.goBack();
$rootScope.$broadcast('selected-unit', { data: unit_id });
}
in the original view controller i capture the event and result.
$rootScope.$on('selected-unit', function(event, args) {
console.log("received selected-unit" + args.data);
$scope.showSelectedUnit = args.data;
});
but it NEVER got updated in the view
<label class="item item-text-wrap">
<button class="button button-block button-outline button-positive" ng-click="performselectUnit()"> Select Unit
</button>
{{showSelectedUnit}}
</label>
How can I get it to update in the view ? or is there a better way
Faced to the exact same issue, I could make it work by switching the order of calls to goBack and broadcast:
$rootScope.$broadcast('selected-unit', { data: unit_id });
$ionicHistory.goBack();
You can use pub-sub service for sharing info between two ctrl
fiddle demo
function MyCtrl($scope, datasharer) {
$scope.sharedData = datasharer.getSharedData();
$scope.send = function() {
datasharer.setSharedData($scope.name);
}
}
function My2Ctrl($scope, datasharer) {
function getSendData(data) {
console.log(data);
$scope.sharedData = data;
}
datasharer.registerForSharedData(getSendData);
}
Using $rootScope.$broadcast and $rootScope.$on should resolve your problem indeed, just use $scope.$apply in $rootScope.$on:
$rootScope.$on('selected-unit', function(event, args) {
console.log("received selected-unit" + args.data);
$scope.$apply(function() {
$scope.showSelectedUnit = args.data;
});
});
What's more, the $rootScope.$broadcast is always expensive, so you could try $rootScope.$emit instead. More about angular event, please refer to https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/.
But the more graceful solution is use Service to share data between controllers, you could refer to Share data between AngularJS controllers.
I have a large list of items. Each item has it's own details.
In my main view/partial, I simply display a large list list of the item names.
When the user clicks on an item, I want the page to go to a partial which works as a "template", displaying information based on which list item is clicked, and hence possibly what the URL looks like. E.g. /listItem1/
This diagram below hopefully sums up what I want to achieve pretty clearly.
How can I do this?
Right now, I have a pretty standard set up in which I have all the information for each list item in an array of object literals, which is contained in a controller injected into the main app module. Like so:
var app = angular.module('app', [/*nodependencies*/]);
var controllers = {};
app.controller(controllers);
controllers.listController = function ($scope){
$scope.list = [
{name: 'List Item 1 Name', detail1: 'blahblah1', detail2: 'blahblah2'},
{name: 'List Item 2 Name', detail1: 'blahblah1', detail2: 'blahblah2'},
{name: 'List Item 3 Name', detail1: 'blahblah1', detail2: 'blahblah2'}
..... and so on
I know how to create basic views/partials as well. But what would be my next steps?
You can do what you want, using the built-in router which ships with AngularJS.
var app = angular.module('app', [/*nodependencies*/])
.config(function($routeProvider) {
$routeProvider
.when('/:itemId', {
templateUrl: '/path/to/partial',
controller : function($scope, $routeParams) {
$scope.item = $routeParams.itemId;
}
})
});
Basically, what the above means, is that if you browse to pdf/item/1
Then you will have access in your controller to $routeParams.itemId which will be equal to 1. You can then do whatever logic is necessary with this information on your partial to show the information you want.
Hope this helps.
Update
Please look at the controller, this is how you would get the param you passed via the URL, you would then do whatever it is you need to do with that param in the controller, and pass the data back to the view.
You can create a small directive that will use the multi-use partial to display each item on the list
Take a look at this working example (http://plnkr.co/edit/0jNVxRg6g3p8uxpustzz?p=preview)
var myApp = angular.module('myApp', []);
myApp.controller('listController', ['$scope', function ($scope) {
$scope.list = [
{
name: 'List Item 1 Name',
url: 'pdfs/item1.pdf',
detail: 'blahblah'
},
{
name: 'List Item 2 Name',
url: 'pdfs/item2.pdf',
detail: 'blahblah'
},
{
name: 'List Item 3 Name',
url: 'pdfs/item3.pdf',
detail: 'blahblah'
}
];
$scope.selectItem = function(item){
$scope.selected = item;
}
}]);
myApp.directive('listItem', [function () {
return {
restrict: 'A',
scope: {
item: '='
},
templateUrl: 'multiple-partial.html',
link: function (scope, element, iAttrs) {
}
};
}])
I'm a newbie to all the Sencha Touch stuff, but until now I'm very enthousiastic about it's capabilities. There is one problem, i somehow can't solve.
I would like to use a Tpl (XTemplate) for a calender view. The idea is to create a div element for every appointment, which i can place within containers to layout them. Somehow i can't get the dataview to work.
I've stripped down my code to the bare minimum: a panel containing a DataView. When i use the itemTpl, everything works fine. But when I use the tpl (with or without the XTemplate) i don't see anything. I checked if it was just a display malfunction (searched for the XXX from the template), but that's not the case.
This is my code:
Ext.define('InfoApp.view.CalendarDay', {
extend: 'Ext.Panel',
xtype: 'calendarday',
requires: [ 'InfoApp.store.sAppointments'],
config: {
title: 'Dag',
layout: 'fit',
items: [
{
xtype: 'dataview',
store: 'appointmentStore',
//itemTpl: [ 'XXX {day} {course}' ] --> Works
tpl: new Ext.XTemplate('<tpl for=".">XXX {day} {course}</tpl>')--> Doesn't Work...
}
]
}
});
Thanks in advance for any suggestions or improvements!
Assuming ST2 and not ST1
From http://docs.sencha.com/touch/2-0/#!/api/Ext.Component-cfg-tpl and the comments on the tpl: config in the docs, it appears there's a bug when using a remote store. Even if your store has data it. tpl: apparently only works right now if your data is hardcoded in data:[]
you can use itemTpl: new XTemplate(), or itemTpl: XTemplate.from('someid') or you can defer specifying until afterwards, grab your dataview and go dv.setItemTpl(new XTemplate()), etc.
Tanks #brink for your answer.
It took me a couple of days, but this worked for me:
// Load the store
var store = Ext.getStore('appointmentStore');
// Get the current range of the store
var data = store.getRange();
// Create a custom template
var tpl = new Ext.XTemplate(<tpl for=".">{day} {course}</tpl>);
// Loop through the data array
var showData = array();
for(var i=0;i<data.length;i++){
showData.push(data[i].data);
}
// Get the panel by ID and push the html template
var panel = Ext.getCmp('appointmentspanel');
panel.updateHtml(tpl.applyTemplate(showData));