easyui的datagrid的使用记录
datagrid是在 table的基础上变化而来的, 而不是在div的基础上来的。 从div来变成 datagrid,样式的设置还是是比较麻烦的。
dg=datagrid 的标题 来源于 columns 属性, 其内容 来源于 url属性。 关键是, 一定要设置这样的属性, 才能给你显示标题和内容, 否则即使你写了 tr等都不会显示
dg的url 最后输出的内容 必须是 json格式: 如果是php的, 则要用echo, 如果是 其他文件,就要用 json数据
最重要的是: dg的前端 和 后台 服务器之间的 通信 , 都是 通过 ajax的方式 来 实现的。 如果在 php中 有 sleep语句的话, 会看到 加载loading...等待的效果。 ajax提交动作 发生在 每次你在前台 点击 下一页等按钮的时候, 前台需要在ajax提交的时候, 向后台服务器 传送数据; 后台需要 返回 json格式的 数据 给 前台。
而且ajax的方式好像都是 post方式。
比如: 分页, 前端 需要 向 后台 传递数据: 一个是page(当前页码,即是第几页?), 一个是 pageSize(一页多少条数据)。 然后后台post获取到 页码和页数, 在 mysql的查询语句中 ,用 limit start rowsCount, 来规范限定 返回的数据。
而且, 后台 除了返回 当前页面需要的部分 rows数据外, 还要返回一个 total总页数。
最后的 返回结果 要写成 json格式的: 这种 类型: {"total": $total, "rows": .....}
dg的主要内容:
一个是加载 dg的内容, 并完成分页;
二个是, 排序,可以只做 初始化的时候 排序;
三是: 设置dg的样式, 有striped, rowNumbers, singSelected为true等等.
只要是ajax服务器端有echo内容的, 即使在前端没有处理、使用 这些数据的代码,比如没有什么alert的, 也是可以看到服务器返回的数据的, 使用firebug的 network的 param, 和response,就可以查看,而且这个其实是查看得很详细很全面 的。
关于isset和 empty在 查询按钮使用时的区别?
isset是判断 $_POST的某个 变量名 是否设置了的, 主要是 用来 判断 按钮 是否被 单击了的, 主要是 防止 页面刷新时 执行代码;
而 !empty 是判断 $_POST的变量名的值是否为空, 主要是用来判断搜索框中是否有输入 内容的, 常用的判断就是:
if(isset($_POST['name']) && !empty($_POST['name'])){...}
php中变量的两边使用 大括号?
主要有三种情形:
- 一是避免 变量 和变量后面的字符串 连在一起 , 从而引起混淆和错误;
- 二是 在 变量的外面两边 使用 大括号, 可以在 单引号中 也可以输出变量的值, 避免使用 点号来连接 字符串的麻烦
- 三是, 在 变量的名称后 使用大括号, 比如
$str{5}
可以输出字符串中的 第n 个字符。常用来判断字符串的长度:if(!isset($str{5})){...}
==============================================
写代码的一个重要方法, 不是从上到下挨着挨着的写, 而是先写结构, 像if...else这些结构, 然后写简单的分支,最后才写重要的最复杂的分支
- dg的 增删改 编辑操作, 类似于百度网盘, 工具栏上 各种操作按钮, 然后在一个 obj={...}的各种 成员变量和成员方法中 来实现这些功能.
- show 和hide方法, 可以同时针对多个 匹配的元素, 比如: 多个按钮 可以同时一次性的 show 或 hide:
$('#btn1, #btn2'). show(); $('#btn1, #btn2'). hide();
关于datagrid的主要内容有:
- 就是dg的内容载入, 通常是通过js的方式, 并且要分页;
- 就是对内容进行 排序, 一般实现初始化的时候排序就好了
- 是设置dg的内容的一些样式, 比如: striped, rowNumbers, singleSelect(但是如果要增删改的话, 通常还不能设为单选择), 列宽自适应等fitColmns. 在列宽自适应的时候, 要同时设置每一列的宽度为100(这个100是百分比)
- 设置工具栏, 增删改的按钮, 以及 实现查找功能. 工具栏 还是 使用 id#tb的方式. 查询的时候, 可能有多个查询条件, 使用 And. 还要注意在查询/查找 /过滤的情况下, 分页功能仍然要是正确的和可用的
要注意查询/ (以及后面的 增删改等操作) 的过程基本上 都是3步走 : 在obj对应的 方法函数中, 先调用 dg的 load方法, 向服务器 传递 查询条件的 键值对; 然后服务器端 接受查询条件,在数据库中进行 处理, 并返回结果; 然后 再在客户端 用 ajax 处理接收到的数据. - 5/6/7章 是进行增删改操作的: 第五章是进行增加操作的 前台处理和数据准备和ajax提交; 第六章是进行删除和修改前台处理和数据准备等; 第七章是讲 增删改的后台服务器操作的
而有些操作是, 共同的: 比如 开始编辑的操作是: beginEdit, 结束编辑的操作是: endEdit.
==========================================
dg的数据加载?
- columns属性是 两个中括号, 每个对象的属性可以有: field, title, checkbox, sortable, width, 数据的对齐方式align/valign, 跨列/行 rowspan, colspan等
- 列的内容, 只能从 url属性中 通过 ajax 远程加载, 要注意的是, url的内容, 是一个 json格式的数组, 数组元素是 json格式的对象 . 如果是从数据库取数据, 必须进行json 格式处理.即:
[{"id": 1, "name": "foo"}, .....]
的形式. 如果 php返回的格式 不是这样的, 将不会加载数据 进来.
比如
public function dgcontent(){
$sort = I('post.sort');
$order = I('post.order');
$page = I('post.page');
$pageSize = I('post.rows');
$from = $pageSize*($page-1);
$dg = M('dg');
$total = $dg -> count();
$result = $dg->field('id, dept, class, name')->order("$sort $order")->limit($from,$pageSize)->select();
$json="";
for($i=0, $j=count($result); $i<$j; $i++){
$json .= json_encode($result[$i]).',';
}
$json ='{"total":'.$total.',"rows":['. substr($json, 0 , -1).']}';
echo $json;
}
==========================================
strip和stripe的区别?
- strip [ strip] : 的意思, 名词时表示: 条, 带, 狭长地带, 带子, 布带的意思, 动词, 则是 '剥夺'; '分割成条带"
- stripe [straip] 的意思 , 是 各种 条纹! 比如 斑马身上的条纹, 横条纹, 竖条纹, 测试压力的复杂条纹之类的.
MySQL按照字段的 汉字的拼音排序,怎么和常规的想象不一样啊?
按照汉字的拼音排序,用的比较多是在人名的排序中,按照姓氏的拼音字母,从A到Z排序; 如果存储姓名的字段采用的是GBK字符集,那就好办了,因为GBK内码编码时本身就采用了拼音排序的方法(常用一级汉字3755个采用拼音排序,二级汉字就不是了,但考虑到人名等都是常用汉字,因此只是针对一级汉字能正确排序也够用了)。 直接在查询语句后面 添加 order by name asc; 查询结果按照姓氏的升序排序; 如果存储姓名的字段采用的是 utf8字符集,需要在排序的时候对字段进行转码;对于的代码是 order by convert(name using gbk) asc; 同样,查询的结果也是按照姓氏的升序排序(所以, 如果存储的是utf8字符集, 因为有一个转码的过程, 所以看到的排序和想象中的不一样.
count(1) 是什么意思?
count(1),其实就是计算一共有多少符合条件的行。
1并不是表示第一个字段,而是表示一个固定值。
其实就可以想成表中有这么一个字段,这个字段就是固定值1,count(1),就是计算一共有多少个1.
同理,count(2),也可以,得到的值完全一样,count('x'),count('y')都是可以的。一样的理解方式。在你这个语句理都可以使用,返回的值完全是一样的。就是计数。
count(*),执行时会把星号翻译成字段的具体名字,效果也是一样的,不过多了一个翻译的动作,比固定值的方式效率稍微低一些。
=====================================
到底用margin还是用top?
主要还是从 语义和你的用途上来区分,虽然都能实现相同的效果。
何时应当使用margin:
- 不需要在border外侧添加空白时。
- 空白处不需要背景(色)时。
- 上下相连的两个盒子之间的空白,需要相互抵消时。如15px + 20px的margin,将得到20px的空白。
何时应当时用padding:
- 需要在border内测添加空白时。
- 空白处需要背景(色)时。
- 上下相连的两个盒子之间的空白,希望等于两者之和时。如15px + 20px的padding,将得到35px的空白。
::: margin是用来隔开元素与元素的间距;padding是用来隔开元素与内容的间隔。
margin用于布局分开元素使元素与元素互不相干;“老死不相往来” 是两家人
padding用于元素与内容之间的间隔,让内容(文字)与(包裹)元素之间有一段“呼吸距离”。 是一家人
======================================
关于js字面对象的定义:
/*在js中定义变量,并不是必须加var的! 而且, 不加var表示的是 "全局变量", 是顶级对象window的成员变量
- 直接定义的对象字面变量, 不像用function定义的类, 可以用var什么的, {}内部只能是 key: val形式的键值对(无序)集合,所以, 即使是
定义成员变量, 也只能用 var_name: some_value的形式来定义, 而不能用 分号语句的形式. 同时, 所有的键值对之间 要用 逗号隔开
*/
//这里的obj没有加var, 就是全局对象window的属性
obj={
rowEdit : undefined,
search: function(){
},
add: function(){
},
update: function(){
},
remove: function(){
},
save:function(){
},
};
js中的几种 假值和 空值? (参考: https://blog.csdn.net/u012739535/article/details/17621247
, https://www.cnblogs.com/yangzhx/p/4019073.html
)
包括: undefined, '', 0, null, false. 这些都叫 假值或空值, 在 if判断中都是假. 但是它们还是有区别的:
它们的类型不同:
typeof( var_name)
可以知道: undefined 的类型是: undefined, 0 的类型是: number, '' 的类型是string, null的类型是: object, 也就是说 null是对象的空值; false是boolean类型的.这些空值相互之间 用 == 判断 , 的结果 是 : 0, false , ''', 三者之间是相等的: 即 0 == false == '' 的结果是true的. 可以认为这三个是 " 假值". 而 undefined==null , 可以认为它们是空值. 但是 undefined和 null 跟 数字相加的结果是不同的: 比如: 10+null =10, 10+undefined = NaN.
所有的假值 用 == 和 空值 比较的 结果 都是 false.
假值 , 有一个实际的对象, 所以 可以用 .toString() 方法.
而空值, 连对象都没有, 所以 不能用 .toString 方法, 否则将抛出异常再就是它们 强制类型转换 : String(var_name) 后的结果不同: 转换成 字符串的时候, 结果分别 跟它们的 "原型字面字符串". 这在字符串拼接 累加的时候要注意.
undefined表示 无效对象, 当定义一个变量未被初始化时, 就是undefined, 而 null是已经被初始化 为空对象. 但是所有的空值和假值 用 === 判断 都是false.
js中(包括其他任何 面向对象的语言) 都是一样的, 如果 要在对象中, (即对象内部) , 引用对象的成员变量, 都应该 加上对象本身的引用this, 否则凡是不加this的变量, 都认为是全局变量 而不是对象本身的成员变量, 这样就会报引用错误! 当然, 如果是在对象的外部, 引用对象的成员, 就要用对象的类名了, 比如: obj.rowEdit.
obj={
rowEdit : undefined,
search: function(){
....
add: function(){
if(!this.rowEdit){ // 这里要加this
$('#save, #cancel').show();
$('#box').datagrid('insertRow',{
index: 0,
row: { }
});
datagrid如何禁止编辑某一行, 某一列或某一个 单元格?
参考 : https://www.cnblogs.com/langhua/p/3672820.html
- 禁止行编辑: 先获取到某一行, 然后根据条件, 不调用beginEdit方法, 直接返回, 其他则调用 beginEdit方法
- 禁止某一列编辑, 要么直接就不用设置 列的 editor属性; 如果设置了editor属性, 则调用 getColumnOption, 返回列的属性op, 然后设置 op.editor={} 为空
- 禁止某一个单元格编辑...
=================================================
dg中, tp中, 凡是从控制器传递到 模板的变量, 比如: $this ->foo = 'foo',
- 那么在 模板中, {$foo}是可以直接解析的! 根本就不需要 再在 {$foo} 的两边再 考虑什么引号了, 不管是单引号, 还是 双引号都不再需要了! 特别是再 href传递变量的时候 加上 引号等 反而会出错. 比如:
url: "{:U('xscontent')}"+'/?class={$class}',
就好了. - 但是 , 如果是 在 alert 等 语句中, 要 使用 模板变量的话, 你就还是需要 加上 引号了, 因为 直接解析模板变量后, 就是 一个 "字符序列", 不是 字符串! 而报错!
dg的后台是如何返回数据, 来填充前台的记录行的?
/*datagrid的返回结果, 来填充 前台的 数据行,必须严格要求json格式, 而且json的格式和属性名必须是:
* "rows:": json_object_array, 即必须是rows, 后面的数据必须是数组, 每个数组元素必须是json格式的对象
* echo '{"total":"11","rows":[{"id":"100", "class":"A_class", "name":"the_Name", "account":"the_account"}]}';
*/
==============================================
一个很重要的注意问题: 在ajax中, 如果 url的目标地址, target是空的字符串时, 该次 ajax 将提交给 "当前页面"! 从而也会得到 ajax 成功success的 结果! 而事实上 这在很多应用场景下 是 不允许的. 所以 你要 判断 一下 你的 ajax url 目标地址 是否为空
比如: 在 dg的 onAfterEdit事件中, 就要判断 url 是否为空, 然后才去调用 ajax.
onAfterEdit: function(rowIndex, rowData, changes){
var inserted = $('#box').datagrid('getChanges', 'inserted');
var updated = $('#box').datagrid('getChanges', 'updated');
var url=info='';
if(inserted.length>0){
url='{:U("add")}';
info='新增';
}
if(updated.length>0){
url='{:U("update")}';
info='修改';
}
/*
这里很重要 , 要判断一下 url是否为空, 否则 即使任意地 双击 一行记录, 再去 双击另外一行记录, 即使没有和数据库进行 修改操作, 也会提示 ajax的success提示信息 , 然而这个时错误的!
*/
if(url!='' && info != ''){
$.ajax({
type: 'post',
url: url,
data: {row: rowData,},
beforeSend: function(){
$('#box').datagrid('loading');
},
success: function(data){
if(data !== false){
$('#box').datagrid('loaded');
$('#box').datagrid('load');
$('#box').datagrid('unselectAll');
$.messager.show({
title: '消息',
msg: '1个班级 '+ info +' 成功',
showType: 'slide',
timeout: 3000
});
obj.rowEdit = undefined;
}
},
});
===================================================
如何禁用datagrid的某一行被 单击选中或 被 双击选中? 参考: https://blog.csdn.net/Dzq_Boyka/article/details/78531217
主要思想是, 在 onClickRow 和 onDblClickRow 事件中 , 必须显示的调用 $('#box').datagrid('unselectRow', rowIndex)
方法, 不能只是 简单地 返回 return.
要想使dg 在 新增的时候, 不能选择行或 双击行 操作, 那么 可以设置一个 标识变量: isAdded , 当 每次 新增的时候, 都重新设置 isAdded = false, 然后, 判断 onClickRow 和 onDblClickRow 事件中 的 isAdded是否 为false, 或者 true
但是又要保证 初次载入 datagrid的时候, 如果不点击 新增的时候, 要能够 选择单行, 或 双击单行操作, 就要 初始化 obj的 isAdded 为true.
obj={
rowEdit : undefined,
isAdded: true, // 这里是 关键!
add: function(){
// 一旦增加的时候, 就要重新初始化isAdded为false, 因为只要保存/取消一次后, isAdded就失效了
this.isAdded=false;
....
}
onDblClickRow: function(rowIndex, rowData){
// 在新增记录的时候, 禁止单击选行 和 双击选行
if(!obj.isAdded){
$('#box').datagrid('unselectRow', rowIndex);
return;
}
// 双击某一行的时候, 首先要关闭之前可能被修改的行
if(obj.rowEdit != undefined){
$('#box').datagrid('endEdit', obj.rowEdit);
obj.rowEdit = undefined;
}
............
},
onClickRow: function(rowIndex, rowData){
if(!obj.isAdded){
$('#box').datagrid('unselectRow', rowIndex);
return;
}
},
=====================================================
分页器: pagination的ajax事件? 参考: https://blog.csdn.net/H12KJGJ/article/details/53672096
- 分页器的分页list , 不是固定的, 也不是什么 倍数, 你可以任意的自由的 设置 任意数值:
pageList 类型array 用法: 用户能改变页面尺寸。pageList 属性定义了能改成多大的尺寸。
代码实例:
$('#pp').pagination({
pageList: [10,20,50,100]
});
分页器中 的一个 页面 由两个 因素来决定: 一个是: pageSize( 每一个页面的最大尺寸), pageNumber(页数), 所以 每一页的第一条数据的索引值就是: pageSize*(pageNumber-1);
这个就是用来 做 数据库的limit的 依据的:
因此 : onSelectPage 事件, 就是 当重新改变了pageSize , 重新选择了 pageNumber后所得到的页面.总共有4个 ajax事件: onSelectPage, onBeforeRefresh, onRefresh, onChangePageSize... 因此, 在dg中改变分页尺寸的事件是 : onChangePageSize的回调函数中写
===========================================
关于bootstrap中的 table中的单元格内容 水平居中和 垂直居中? 参考: https://blog.csdn.net/peng_hong_fu/article/details/70662979
- 水平居中是: 写 text-center类, 而垂直居中, 则是 写 stye的 vertical-align: middle
- 要注意的是: th的水平居中, 要写在 th单元格内 , 不能写在th的父元素 tr中; 普通的td的水平居中可以写在父元素 tr中
而垂直居中 , 不能简单的写: tbody tr td的样式, 而是要 写 .table tbody tr td. 因为后者的 css优先级为 10+1+1+1 =13, 而前者的css优先级是: 1+1+1=3. 所以 要 优先执行 后者的css. 而后者的css正是在 bootstrap中定义的, 默认为top. 所以 你简单的写 tbody tr td是改变不了的, 要被 bs的所覆盖, 要想在 style 标签中 " 覆盖" bs的默认设置的话, 需要写 完整: .table tbody tr td - 总之, 选择符越详细越具体, 它的css优先级 值就越大. 优先级 就越高
css的优先级? 参考 https://blog.csdn.net/amyleeYMY/article/details/63685330
由四位数字 组成:
!important =1000,
id的优先级=100,
类, 伪类, 属性的优先级 =10,
元素, 伪元素的优先级=1
这些优先级, 不管层次, 只要有一个就 加上相应的 优先级 数值. 最后 算 总和.
但是要注意:
伪元素 只有四个, 即 :before, :after, : first-letter, :first-line , 主要是表示位置的
伪类 有更多, 只要是表示 "状态"的, 比如 :link, :active, :visited, :hover, :focus, :first-child 等如果有多个 相互冲突的 css规则 同时作用在同一个 元素上, 则最终 起作用的是: 以 定义这些类样式的先后顺序为准, 后定义的样式 会 覆盖 先前定义的样式, 即 后定义的样式 最终 将起作用. 而跟 该元素上 , 多个类样式 书写的先后顺序无关.
easyui的消息框 是异步的, 如何理解?
异步, 表示 它是 "非模态的", 虽然有 "遮照" 样式, 但是 它并不会阻止 该消息框 后续的代码的执行. 相反, 在执行 消息框的 回调函数时, 主函数的 剩余代码 已经执行完毕了! 所以这就是 为什么主函数中 无法获取 回调函数的 返回值的原因
那么 要想实现 模态 框的 效果, 想要 某些代码 在 点击 "确定" 后, 再执行, 就要把 这些代码 放在 消息框的 回调函数中, 因为 回调函数 总数在 用户 单击 "确定" 按钮后 才执行.
easyui的datagrid的使用记录的更多相关文章
- 【技巧】easyUI的datagrid,如何在翻页以后仍能记录被选中的行
easyUI的datagrid在复选框多选时,如何在翻页以后仍能记录被选中的行: 注意datagrid中需要配置idField属性,一般为数据的主键
- Easyui datagrid 实现表格记录拖拽
datagrid 实现表格记录拖拽 by:授客 QQ:1033553122 测试环境 jquery-easyui-1.5.3 jquery-easyui-datagrid-dnd 下载地址: http ...
- EasyUI的datagrid分页
EasyUI的datagrid分页 前台代码: <script type="text/javascript"> $(function () { //查询 search( ...
- 利用Aspose.Cells完成easyUI中DataGrid数据的Excel导出功能
我准备在项目中实现该功能之前,google发现大部分代码都是利用一般处理程序HttpHandler实现的服务器端数据的Excel导出,但是这样存在的问题是ashx读取的数据一般都是数据库中视图的数据, ...
- Easyui的datagrid结合hibernate实现数据分页
最近在学习easyui的使用,在学到datagrid的时候遇到了一些问题,终于抽点时间整理了一下,分享出来,请各位前辈高手多多指教! 1.先来看看效果,二话不说,上图直观! 2.easyui的data ...
- SSh结合Easyui实现Datagrid的分页显示
近日学习Easyui,发现非常好用,界面很美观.将学习的心得在此写下,这篇博客写SSh结合Easyui实现Datagrid的分页显示,其他的例如添加.修改.删除.批量删除等功能将在后面的博客一一写来. ...
- 按CTRL,SHIFT,ALT等键扩展easyui的datagrid多选实现
//------------------------------------------------------------------------------- // 当然页面文件中还需要引入的文件 ...
- 实例:SSH结合Easyui实现Datagrid的批量删除功能
在我先前的基础上面添加批量删除功能.实现的效果如下 删除成功 通常情况下删除不应该真正删除,而是应该有一个标志flag,但flag=true表示状态可见,但flag=false表示状态不可见,为删除状 ...
- 实例:SSH结合Easyui实现Datagrid的新增功能和Validatebox的验证功能
在我前面一篇分页的基础上,新增了添加功能和添加过程中的Ajax与Validate的验证功能.其他的功能在后面的博客写来,如果对您有帮助,敬请关注. 先看一下实现的效果: (1)点击添加学生信息按键后跳 ...
随机推荐
- ThinkPHP5中如何实现模板完全静态化
模板完全静态化,也就是通过模板完全生成纯静态的网页,相比动态页面和伪静态页面更安全更利于SEO访问更快.相比前二者各有利弊吧,现在稍微对这三种形式的优缺点对比一下,以及在ThinkPHP5项目中实现完 ...
- 【优化】COUNT(1)、COUNT(*)、COUNT(常量)、COUNT(主键)、COUNT(ROWID)、COUNT(非空列)、COUNT(允许为空列)、COUNT(DISTINCT 列名)
[优化]COUNT(1).COUNT(*).COUNT(常量).COUNT(主键).COUNT(ROWID).COUNT(非空列).COUNT(允许为空列).COUNT(DISTINCT 列名) 1. ...
- 设计模式之动态代理(JDK代理)
动态代理跟静态代理一个很重要的区别在于,动态代理是在内存是中的,是在代码编译期后在内存是实现的,而静态代理是我们自己编写代理类,编译后生成class文件.动态代理需要借助两个类:java.lang.r ...
- IDEA创建本地Spark程序,并本地运行
1 IDEA创建maven项目进行测试 v创建一个新项目,步骤如下: 选择“Enable Auto-Import”,加载完后:选择“Enable Auto-Import”,加载完后: 添加SDK依 ...
- kubelet 预留system、kube资源
kubelet 预留system.kube资源 Kubernetes 的节点可以按照 Capacity 调度.默认情况下 pod 能够使用节点全部可用容量.这是个问题,因为节点自己通常运行了不少驱动 ...
- Linux运维技术之yum与rpm的基本使用要点
https://pkgs.org/ 与https://rpmfind.org/ RPM包下载 RPM包简介 1.安装与升级时,使用的是包全名 2.RPM包安装时要注意包的依赖性 RPM包操作(系统 ...
- zabbix--远程执行命令
zabbix 远程执行命令 重启应用 服务器 使用远程执行命令可以在某些时候帮我做一些事情,达到轻量级的自动化,比如当 nginx.mysql.php.redis.tomcat.等等应用挂掉时帮我们自 ...
- c#每天生成漂亮桌面背景、英文名言、翻译
阅读目录 一.1. 下载bing.com壁纸查询API 二.2. 解析返回的壁纸JSON信息 三.3. 下载完成的壁纸图片 阅读目录 .NET生成漂亮桌面背景 .NET生成漂亮桌面背景 总结 回到目录 ...
- spark jdbc(mysql) 读取并发度优化
转自:https://blog.csdn.net/lsshlsw/article/details/49789373 很多人在spark中使用默认提供的jdbc方法时,在数据库数据较大时经常发现任务 h ...
- NSSting NSData 与字符集合
NSString 是为了人类阅读而存在的,必须进行编码,以使得bytes对人类有意义 String Objects An NSString object encodes a Unicode-compl ...