I am dynamically building a TableView in my controller, which works fine: The initial table displays the initial collections data as expected.
The problem is that the TableView's 'click' event listener is ignored when I click on the table rows. I am testing in the browser, and I never even see the console event file (see comments in controller file). All relevant code snippets below:
In my alloy.js I setup a backbone collection:
function defaultTodo(name) { return {name: name, done: false}; }
function doneTodo(name) { return {name: name, done: true}; }
Alloy.Collections.todos = new Backbone.Collection();
Alloy.Collections.todos.reset([
defaultTodo('Apples'), // create not yet done todo
defaultTodo('Banana'),
defaultTodo('Paper Towels'),
defaultTodo('Broccoli'),
doneTodo('Beans'), // create already done todo
doneTodo('Water'),
doneTodo('Blueberries'),
doneTodo('Stir Fry')
])
Here is my index.js controller:
var todos = Alloy.Collections.todos;
function redrawTable() {
// clear all the old data
// See http://developer.appcelerator.com/question/49241/delete-all-rows-in-a-tableview-with-a-single-click
$.table.setData([]);
// Create and add the TableViewSections
var alreadyDone = Ti.UI.createTableViewSection({ headerTitle: "Already Done" });
var needsDoing = Ti.UI.createTableViewSection({ headerTitle: "Needs Doing" });
$.table.appendSection(needsDoing);
$.table.appendSection(alreadyDone);
// Add the todo to the appropriate sections
todos.forEach(function(todo) {
var section = todo.get('done') ? alreadyDone : needsDoing;
addEntry(todo, section);
});
// Add the click listener
// THIS LISTENER IS IGNORED ********************************
$.table.addEventListener('click', function(e) {
console.log(e);
todos.at(e.index).set('done', true);
todos.trigger('change');
});
// Helper function to add a row to a section
function addEntry(todo, section) {
var row = Ti.UI.createTableViewRow({
title: todo.get('name'),
className: "row"
});
section.add(row);
}
}
// Redraw our table each time our todos collection changes
todos.on('change', redrawTable);
// Trigger a change event to draw the initial table
todos.trigger('change');
$.index.open();
And here is index.xml view file:
<Alloy>
<Window class="container">
<Label id="test" class="header">My Grocery List</Label>
<TextField id="newItem"/>
<TableView id="table">
</TableView>
</Window>
</Alloy>
UPDATE: Working Code
In addition to the changes below, I also added onClick="markDone" to the xml.
function markDone(e) {
console.log(e.row.todo);
e.row.todo.set('done', true);
todos.trigger('change');
}
function redrawTable() {
// clear all the old data
// See http://developer.appcelerator.com/question/49241/delete-all-rows-in-a-tableview-with-a-single-click
$.table.setData([]);
var rows = [];
var done = [];
var doing = [];
// Add the todo to the appropriate sections
todos.forEach(function(todo) {
var row = Ti.UI.createTableViewRow({
title: todo.get('name'),
className: "row"
});
row.todo = todo;
todo.get('done') ? done.push(row) : doing.push(row);
});
// Create and add the TableViewSections
rows.push(Ti.UI.createTableViewSection({ headerTitle: "Needs Doing" }));
rows = rows.concat(doing);
rows.push(Ti.UI.createTableViewSection({ headerTitle: "Already Done" }));
rows = rows.concat(done);
$.table.setData(rows);
};
I created brand new project using files which you provided and eventListener is working perfectly fine. However there are couple other bugs:
Creating listener inside redrawTable() function, which is executed every time you click on something in TableView. As a result at the beginning you have one eventListener but after every click all listeners are duplicated.
Using index property in event handler to find index of Backbone model object to update. index property is indicating at which place given row was displayed in your TableView. When you are moving rows between sections their index are changing. In your case it's better to check e.row.name property and use Backbone.Collection.findWhere() method. If user can have two items with the same name on the list, then you have to create additional property to determine which object in model should be changed.
You should add rows to section before section are added to table. In your case table is very simple so instead of doing loops you can just create one simple array of objects (with title and optional header properties) and pass it to $.table.setData().
It's good to wait for postlayout event triggered on main view before triggering any custom events to make sure that the whole view was created and all objects are initiated.
Check rewrote index.js code below to see how it could be done.
var todos = Alloy.Collections.todos;
function redrawTable() {
var done = todos.where({done: true}).map(function(elem) {
return { title: elem.get('name') };
});
if (done.length > 0) {
done[0].header = 'Already Done';
}
var notdone = todos.where({done: false}).map(function(elem) {
return { title: elem.get('name') };
});
if (notdone.length > 0) {
notdone[0].header = 'Needs Doing';
}
$.table.setData( done.concat(notdone) );
}
$.table.addEventListener('click', function(e) {
todos.where({ name: e.row.title }).forEach(function(elem){
elem.set('done', !elem.get('done'));
});
todos.trigger('change'); // Redraw table
});
// Redraw our table each time our todos collection changes
todos.on('change', redrawTable);
// Trigger a change event to draw the initial table
$.index.addEventListener('postlayout', function init(){
todos.trigger('change');
// Remove eventListener, we don't need it any more.
$.index.removeEventListener('postlayout', init);
})
$.index.open();
redrawTable() could have a little more refactor but I left it so it's easier to read.
To read more about manipulating TableView check this Appcelerator documentation page.
Related
Hi this question is related with my own answer here the thing is that the widget run only once, when the first database record object is show on the form view, but when I change to another record, the view its not updated with the actual record. I think that is because I run all the code in the ´start´ but I dont know how and where put he code to do this.
the code again:
(function (instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
openerp.chess_base = function (instance, local) {
local.ShowBoard = instance.web.form.FormWidget.extend({
start: function () {
this.$el.append('<div id="board" style="width: 300px">BOARD GOES HERE</div>');
this.show_board();
},
show_board: function () {
var Game = new instance.web.Model("chess.game"),
record_id = this.field_manager.datarecord.id,
record_name = this.field_manager.datarecord.name,
self = this;
self.el_board = self.$('#board');
Game.query(['pgn']).filter([['id', '=', record_id], ['name', '=', record_name]]).all().then(function (data) {
console.log(data);
self.cfg = {
position: data[0].pgn,
orientation: 'white',
pieceTheme: '/chess_base/static/img/chesspieces/wikipedia/{piece}.png'
};
ChessBoard(self.el_board, self.cfg);
});
}
});
instance.web.form.custom_widgets.add('board', 'instance.chess_base.ShowBoard');
}
})(openerp);
In your start function:
this._ic_field_manager.on('view_content_has_changed', this, function() {
'put your code here'
})
EDIT:
Actually there's another event you could listen, 'load_record'.
Because 'view_content_has_changed' is triggered every time a single field in the view is modified, and you maybe don't want this behavior.
I have a partial view for cascading drop downs i.e. Country and State. I am using the following razor view statement to render this partial view
#{Html.RenderAction("PopulateCountriesDropdown", "Helper");}
and it works just fine. Following is the complete code for the partial view that makes an asynchronous call to an action method of the controller
<pre>#model OnlineExamSystem.Models.CountryAndStateViewModel
#Html.LabelFor(m => m.c.CountryName)
#Html.DropDownListFor(m => m.c.CountryId, new SelectList(Model.cntlist, "CountryId", "CountryName"),"--Select Country--", new { #class="aha" })
#Html.Label("State")
Note: since i am unable to write HTML i.e. I have a simple html select for displaying 2nd drop down to show states with class="ddlstates"
<script type="text/javascript">
$(document).ready(function () {
$(".aha").change(function () {
var Url = '/Helper/PopulateStateDropdown';
var catId = $(this).val();
//alert(catId);
var select = $('.ddlstate');
if (catId != '') {
$.getJSON("/Helper/PopulateStateDropdown", { id: catId },
function (ddl) {
select.empty();
select.append($("<option></option>", { value: 0, text: '--Select State--' }));
$.each(ddl, function (index, itemData) {
select.append($("<option></option>", { value: itemData.Value, text: itemData.Text }));
});
});
}
else {
select.empty();
select.append($("<option></option>", { value: 0, text: '--Select State--' }));
}
});
});
as i said this works just fine. but here is the problem i.e. when I try to render the same partial view again on the same page (view) as following
#{Html.RenderAction("PopulateCountriesDropdown", "Helper");}
the rendering is ok, but changing the country in the 2nd partial view does not select the states properly. Also I have noticed that following action method is called twice for the first partial view
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult PopulateStateDropdown(string id)
{
var ls = State.GetStateByCountryId(Int32.Parse(id)).AsEnumerable();
var ddl = ls.Select(m => new SelectListItem() { Text = m.StateName, Value = m.StateId.ToString() });
return Json(ddl, JsonRequestBehavior.AllowGet);
}
and interestingly the the above method is not at all called from the 2nd partial view.
(doucment).ready() binds event to the dome elements when the page is loaded first time, it finds the elements in DOM and bind events to them, in your case as its a partial view, html is rendered dynamically on page after page is loaded so event is not binded.
Use live function for event as you html is dynamically added on the view:
Do like this:
$(".aha").live('change',function () {
});
I'm trying to select the first item in my listview control as soon as my nav page loads. The following code does not seem to have an effect. If i put the "selection.set(0)" code inside of a button click handler it will work fine, but it will not work as soon as the page loads which is my desired effect. Anyone have any ideas? I must be missing something very basic here!
HTML
<div id="basicListView"
data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : ex.itemList.dataSource,
itemTemplate: select('#mediumListIconTextTemplate'),
layout : {type: WinJS.UI.ListLayout},
selectionMode: 'single',
tapBehavior: 'directSelect'
}">
</div>
JS
(function () {
"use strict";
var _lv = null;
WinJS.UI.Pages.define("/pages/page2/page2.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
// TODO: Initialize the page here.
_lv = document.getElementById("basicListView").winControl;
// This should select the first item in the listview though it seems to have no effect.
_lv.selection.set(0);
},
});
})();
I think your problem is that the list is not populated when you try to access it.
Try to put this code in your ready event so you will know when the list is ready:
ready: function (element, options) {
var listView = element.querySelector(".itemslist").winControl;
listView .onloadingstatechanged = function (args) {
if (args.srcElement === listView .element && listView .loadingState === "complete") {
//and here you set your item
_lv = document.getElementById("basicListView").winControl;
_lv.selection.set(0);
}
};
}
I am currently trying to implement automatic filtering in Yii cGridview, By default it filters 'onclick', or 'enter' key press, But I need to change that event to "onkeyup"|
my code is like this
Yii::app()->clientScript->registerScript('search',"
$('.filters > td >input').keyup(function(){
$('#grid-id').yiiGridView('update', {
data: $(this).serialize()
});
return false;
});
");
?>
when I entered the first letter filtering occured, but after filtering and rendering the code fails.. please give me a solution.. Is there any php yii gridview extension which has filtering onkeyup
You need to change the way you attach the keyup listeners. After the gridview refreshed through AJAX, all elements inside the grid are replaced. So there's no keyup attached anymore. You can try something like:
$('body').on('keyup','.filters > td > input', function() {
$('#grid-id').yiiGridView('update', {
data: $(this).serialize()
});
return false;
});
#Michael Härtl's answer is right. But 2 Problem occur when you use this code.
1) When User Search in filter at that time, every time grid will be refresh so focus of input box will be lost.
2) When you search in one filter input and if you go to second input field field at that time first input box will be lost.
So now I have got the solution for that.
Set this java script code on your grid view.
Yii::app()->clientScript->registerScript('search', "
$('body').on('keyup','.filters > td > input', function() {
$(document).data('GridId-lastFocused',this.name);
data = $('#GridId input').serialize();
$('#GridId').yiiGridView('update', {
data: data
});
return false;
});
// Configure all GridViews in the page
$(function(){
setupGridView();
});
// Setup the filter(s) controls
function setupGridView(grid)
{
if(grid==null)
grid = '.grid-view tr.filters';
// Default handler for filter change event
$('input,select', grid).change(function() {
var grid = $(this).closest('.grid-view');
$(document).data(grid.attr('id')+'-lastFocused', this.name);
});
}
// Default handler for beforeAjaxUpdate event
function afterAjaxUpdate(id, options)
{
var grid = $('#'+id);
var lf = $(document).data(grid.attr('id')+'-lastFocused');
// If the function was not activated
if(lf == null) return;
// Get the control
fe = $('[name=\"'+lf+'\"]', grid);
// If the control exists..
if(fe!=null)
{
if(fe.get(0).tagName == 'INPUT' && fe.attr('type') == 'text')
// Focus and place the cursor at the end
fe.cursorEnd();
else
// Just focus
fe.focus();
}
// Setup the new filter controls
setupGridView(grid);
}
// Place the cursor at the end of the text field
jQuery.fn.cursorEnd = function()
{
return this.each(function(){
if(this.setSelectionRange)
{
this.focus();
this.setSelectionRange(this.value.length,this.value.length);
}
else if (this.createTextRange) {
var range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', this.value.length);
range.moveStart('character', this.value.length);
range.select();
}
return false;
});
}");
Add this line to your gridview widget code.
'afterAjaxUpdate'=>'afterAjaxUpdate',
For example:
$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'GridId',
'afterAjaxUpdate'=>'afterAjaxUpdate',
));
I'm having problems to understand how to implement new WP media uploader into my theme options page. Is there a documentation on how to do this or some explanation what-so-ever? I have seen couple of samples of how to do this but none of them has any good explanation about their code. Is there list of options how to customize media uploader frame? I mean wouldn't it be good if you can do something like this (See // Create the media frame.):
// Uploading files
var file_frame;
jQuery('.upload_image_button').live('click', function() {
// If the media frame already exists, reopen it.
if ( file_frame ) {
file_frame.open();
return;
}
// Create the media frame.
file_frame = wp.media.frames.file_frame = wp.media({
title: 'My frame title',
button: {
text: 'My button text',
},
id: 'logo-frame',
multiple: false,
editing_sidebar: false, // Just added for example
default_tab: 'upload', // Just added for example
tabs: 'upload, library', // Just added for example
returned_image_size: 'thumbnail' // Just added for example
});
// When an image is selected, run a callback.
file_frame.on( 'select', function() {
var attachment;
// We set multiple to false so only get one image from the uploader
attachment = file_frame.state().get('selection').first().toJSON();
// Do something with attachment.id and/or attachment.url here
});
// Finally, open the modal
file_frame.open();
return false
});
For WP 3.5, you can use the new media uploader. I'll be brief in the hopes that you know what you're doing. The idea is to call the wp_enqueue_script (this only works on WP >= 3.5 btw). Once the script is called, you can manipulate the javascript object. You'll have to do some inspecting to see your full set of options.
First you have to enqueue the script:
add_action( 'wp_enqueue_scripts', 'front_upload_enqueues' );
function front_upload_enqueues() {
wp_register_script('uploads',
// path to upload script
get_template_directory_uri().'/lib/js/media-upload.js'
);
wp_enqueue_script('uploads');
if ( function_exists('wp_enqueue_media') ) {
// this enqueues all the media upload stuff
wp_enqueue_media();
}
}
Then you have to add the javascript (jQuery in my case):
jQuery(document).ready(function($){
var frame;
/*
* Upload button click event, which builds the choose-from-library frame.
*
*/
$('.form-table').on('click', '.member-upload-field .btn-upload', function( event ) {
var $el = $(this);
event.preventDefault();
// Create the media frame.
frame = wp.media.frames.customHeader = wp.media({
title: $el.data('choose'),
library: { // remove these to show all
type: 'image', // specific mime
author: userSettings.uid // specific user-posted attachment
},
button: {
text: $el.data('update'), // button text
close: true // whether click closes
}
});
// When an image is selected, run a callback.
frame.on( 'select', function() {
// Grab the selected attachment.
var attachment = frame.state().get('selection').first(),
link = $el.data('updateLink');
$el.prev('input').val( attachment.attributes.id );
$el.parent().prev().find('img').attr('src', attachment.attributes.url );
});
frame.open();
});
});