PageRenderTime 68ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/target/ribs.js

https://github.com/tehmou/Ribs.js
JavaScript | 906 lines | 811 code | 89 blank | 6 comment | 150 complexity | 04d4bd4c0e5d6d37a3919ba3a7950829 MD5 | raw file
  1. // Core classes
  2. /*global $,_,Backbone,console*/
  3. var Ribs = {};
  4. Ribs.VERSION = '0.0.82';
  5. Ribs.mixins = {};
  6. Ribs.mixinMethods = [
  7. "customInitialize",
  8. "modelChanging", "modelChanged",
  9. "render", "redraw", "refresh",
  10. "unbindEvents", "bindEvents",
  11. "hide", "dispose"
  12. ];
  13. Ribs.createMixed = function (myOptions) {
  14. myOptions = myOptions || {};
  15. var Buildee = Ribs.ManagedView.extend(),
  16. NewRootMixin = Ribs.mixins.mixinComposite(myOptions),
  17. OldRootMixin = myOptions.base && myOptions.base.RootMixin,
  18. delegateOneToRootMixin = function (methodName) {
  19. Buildee.prototype[methodName] = function () {
  20. Ribs.ManagedView.prototype[methodName].apply(this, arguments);
  21. if (this.rootMixin && this.rootMixin[methodName]) {
  22. this.rootMixin[methodName].apply(this.rootMixin, arguments);
  23. }
  24. };
  25. };
  26. _.each(Ribs.mixinMethods, delegateOneToRootMixin);
  27. if (OldRootMixin) {
  28. Buildee.RootMixin = Ribs.mixins.mixinComposite({
  29. mixinClasses: [OldRootMixin, NewRootMixin]
  30. });
  31. } else {
  32. Buildee.RootMixin = NewRootMixin;
  33. }
  34. Buildee.prototype.initialize = function () {
  35. this.rootMixin = new Buildee.RootMixin(this, this.options.model);
  36. Ribs.ManagedView.prototype.initialize.apply(this, arguments);
  37. };
  38. return Buildee;
  39. };
  40. Ribs.ManagedView = Backbone.View.extend({
  41. invalidated: true,
  42. initialize: function () {
  43. _.bindAll(this, "customInitialize", "bindToModel", "modelChanging", "modelChanged", "render", "unbindEvents", "redraw", "refresh", "bindEvents", "hide", "dispose");
  44. Backbone.View.prototype.initialize.apply(this, arguments);
  45. if (this.model) {
  46. this.bindToModel(this.model);
  47. }
  48. this.customInitialize();
  49. this.initialized = true;
  50. this.render();
  51. },
  52. customInitialize: function () { },
  53. bindToModel: function (model) {
  54. this.modelChanging();
  55. if (this.model && this.model.ribsUI) {
  56. this.model.ribsUI.safeUnbind("all", this.render);
  57. }
  58. this.model = model;
  59. if (this.model) {
  60. Ribs.augmentModelWithUIAttributes(this.model);
  61. this.model.ribsUI.bind("all", this.render);
  62. }
  63. this.invalidated = true;
  64. this.modelChanged(model);
  65. },
  66. modelChanging: function () { },
  67. modelChanged: function (newModel) { },
  68. render: function () {
  69. if (!this.initialized) { return; }
  70. this.unbindEvents();
  71. if (this.invalidated) {
  72. this.redraw(this.el);
  73. this.invalidated = false;
  74. }
  75. this.refresh();
  76. this.bindEvents();
  77. },
  78. unbindEvents: function () {
  79. $(this.el).unbind();
  80. },
  81. bindEvents: function () { },
  82. redraw: function (el) { },
  83. refresh: function () { },
  84. hide: function () {
  85. $(this.el).detach();
  86. },
  87. dispose: function () {
  88. $(this.el).remove();
  89. }
  90. });
  91. Ribs.mixins.mixinComposite = function (classOptions) {
  92. classOptions = classOptions || {};
  93. var elementSelector = classOptions.elementSelector,
  94. elementCreator = classOptions.elementCreator,
  95. mixinClasses = classOptions.mixinClasses || Ribs.parseMixinDefinitions(classOptions.mixins),
  96. callAllMixins = function (mixins, methodName, originalArguments) {
  97. _.each(mixins, function (mixin) {
  98. if (mixin[methodName]) {
  99. mixin[methodName].apply(mixin, originalArguments);
  100. }
  101. });
  102. },
  103. updateMixinEl = function (mixin, el) {
  104. mixin.el = mixin.elementSelector ? el.find(mixin.elementSelector) : el;
  105. },
  106. updateMixinMyValue = function (mixin) {
  107. if (mixin.attributeName && mixin.dataModel) {
  108. mixin.myValue = mixin.dataModel.get(mixin.attributeName);
  109. } else if (mixin.uiAttributeName) {
  110. mixin.myValue = mixin.uiModel.get(mixin.uiAttributeName);
  111. } else {
  112. mixin.myValue = null;
  113. }
  114. },
  115. eventSplitter = /^(\w+)\s*(.*)$/,
  116. MixinCompositeInst = function (parentView, model) {
  117. this.customInitialize = function () {
  118. this.mixins = [];
  119. _.each(mixinClasses, _.bind(function (MixinClass) {
  120. var mixin = new MixinClass(parentView, model);
  121. _.bind(function () { _.bindAll(this); }, mixin)();
  122. mixin.uiModel = (model && model.ribsUI) || new Backbone.Model();
  123. if (mixin.modelChanging) {
  124. mixin.modelChanging();
  125. }
  126. mixin.dataModel = model;
  127. updateMixinMyValue(mixin);
  128. if (mixin.modelChanged) {
  129. mixin.modelChanged(mixin.dataModel);
  130. }
  131. this.mixins.push(mixin);
  132. }, this));
  133. callAllMixins(this.mixins, "customInitialize", arguments);
  134. };
  135. this.unbindEvents = function () {
  136. if (this.el) {
  137. this.el.unbind();
  138. }
  139. _.each(this.mixins, function (mixin) {
  140. if (mixin.el) {
  141. mixin.el.unbind();
  142. }
  143. if (mixin.unbindEvents) {
  144. mixin.unbindEvents.apply(mixin);
  145. }
  146. });
  147. };
  148. this.bindEvents = function () {
  149. _.each(this.mixins, function (mixin) {
  150. if (mixin.bindEvents) {
  151. mixin.bindEvents.apply(mixin);
  152. }
  153. if (!mixin || !mixin.events || !mixin.el || !mixin.el.is(":visible")) {
  154. return;
  155. }
  156. _.each(mixin.events, _.bind(function (methodName, key) {
  157. var match = key.match(eventSplitter),
  158. eventName = match[1], selector = match[2],
  159. method = _.bind(this[methodName], this);
  160. if (selector === '') {
  161. mixin.el.bind(eventName, method);
  162. } else {
  163. mixin.el.delegate(selector, eventName, method);
  164. }
  165. }, mixin));
  166. });
  167. };
  168. this.modelChanged = function (newModel) {
  169. _.each(this.mixins, _.bind(function (mixin) {
  170. mixin.dataModel = newModel;
  171. mixin.uiModel = newModel ? newModel.ribsUI : new Backbone.Model();
  172. updateMixinMyValue(mixin);
  173. if (mixin.modelChanged) {
  174. mixin.modelChanged.apply(mixin, [newModel]);
  175. }
  176. }, this));
  177. };
  178. this.redraw = function (parentEl) {
  179. this.el = $(parentEl).find(elementSelector);
  180. if (this.el.length === 0) {
  181. if (elementCreator) {
  182. this.el = $(parentEl).append($(elementCreator));
  183. } else {
  184. this.el = $(parentEl);
  185. }
  186. }
  187. _.each(this.mixins, _.bind(function (mixin) {
  188. updateMixinMyValue(mixin);
  189. updateMixinEl(mixin, this.el);
  190. if (mixin.redraw) {
  191. mixin.redraw.apply(mixin, [mixin.el]);
  192. }
  193. }, this));
  194. };
  195. this.refresh = function () {
  196. _.each(this.mixins, _.bind(function (mixin) {
  197. updateMixinMyValue(mixin);
  198. if (mixin.refresh) {
  199. mixin.refresh.apply(mixin);
  200. }
  201. }, this));
  202. };
  203. };
  204. _.each(Ribs.mixinMethods, function (methodName) {
  205. if (!MixinCompositeInst.prototype.hasOwnProperty(methodName)) {
  206. MixinCompositeInst.prototype[methodName] = function () {
  207. callAllMixins(this.mixins, methodName, arguments);
  208. };
  209. }
  210. });
  211. return MixinCompositeInst;
  212. };
  213. // Utilities
  214. Ribs.parseMixinDefinitions = function (mixinDefinitions) {
  215. mixinDefinitions = mixinDefinitions || [];
  216. var mixinClasses = [], i, l,
  217. processMixinDefinition = function (options, name) {
  218. var mixinFunction = Ribs.mixins[name];
  219. if (!mixinFunction) {
  220. throw "Could not find mixin " + name;
  221. }
  222. mixinClasses.push(mixinFunction(options));
  223. };
  224. if (_.isArray(mixinDefinitions)) {
  225. for (i = 0, l = mixinDefinitions.length; i < l; i++) {
  226. var mixinDefinitionObject = mixinDefinitions[i];
  227. _.each(mixinDefinitionObject, processMixinDefinition);
  228. }
  229. } else {
  230. _.each(mixinDefinitions,
  231. function (nestedMixinDefinitionArray, elementSelector) {
  232. var MixinComposite = Ribs.mixins.mixinComposite({
  233. mixins: nestedMixinDefinitionArray,
  234. elementSelector: elementSelector
  235. });
  236. mixinClasses.push(MixinComposite);
  237. }
  238. );
  239. }
  240. return mixinClasses;
  241. };
  242. Ribs.NonSyncingCollection = Backbone.Collection.extend({
  243. add: function (item) {
  244. var oldCollection = item.collection;
  245. Backbone.Collection.prototype.add.apply(this, arguments);
  246. item.collection = oldCollection;
  247. },
  248. remove: function (item) {
  249. var oldCollection = item.collection;
  250. Backbone.Collection.prototype.remove.apply(this, arguments);
  251. item.collection = oldCollection;
  252. }
  253. });Ribs.createUIManager = function (key, myOptions) {
  254. myOptions = myOptions || {};
  255. Ribs.uiManagers = Ribs.uiManagers || {};
  256. Ribs.uiManagers[key] = (function () {
  257. var allowMultiselect = myOptions.allowMultiselect,
  258. viewModel = new Backbone.Model({ nowHovering: null, nowSelected: null }),
  259. hoveringChanged = function (event) {
  260. var item = event[0];
  261. if (item === viewModel.get("nowHovering") && !item.get("hovering")) {
  262. viewModel.set({ nowHovering: null });
  263. } else if (item !== viewModel.get("nowHovering") && item.get("hovering")) {
  264. var lastHovering = viewModel.get("nowHovering");
  265. viewModel.set({ nowHovering: item });
  266. if (lastHovering) {
  267. lastHovering.set({ hovering: false });
  268. }
  269. }
  270. },
  271. selectedChanged = function (event) {
  272. var item = event[0];
  273. if (item === viewModel.get("nowSelected") && !item.get("selected")) {
  274. viewModel.set({ nowSelected: null });
  275. } else if (item !== viewModel.get("nowSelected") && item.get("selected")) {
  276. var lastSelected = viewModel.get("nowSelected");
  277. viewModel.set({ nowSelected: item });
  278. if (!allowMultiselect && lastSelected) {
  279. lastSelected.set({ selected: false });
  280. }
  281. }
  282. },
  283. unregister = function (model) {
  284. if (model) {
  285. model.unbind("ribsUI:change:hovering", hoveringChanged);
  286. model.unbind("ribsUI:change:selected", selectedChanged);
  287. }
  288. },
  289. register = function (model) {
  290. if (model) {
  291. unregister(model);
  292. model.bind("ribsUI:change:hovering", hoveringChanged);
  293. model.bind("ribsUI:change:selected", selectedChanged);
  294. }
  295. };
  296. return {
  297. register: register,
  298. unregister: unregister,
  299. getViewModel: function () { return viewModel; }
  300. };
  301. }());
  302. };
  303. Ribs.augmentModelWithUIAttributes = function (model) {
  304. if (!model.hasOwnProperty("ribsUI")) {
  305. model.ribsUI = new Backbone.Model();
  306. // Do this until the next version of Backbone.js:
  307. // https://github.com/documentcloud/backbone/issues/309
  308. model.ribsUI.safeUnbind = function (ev, callback) {
  309. var calls, i, l, emptyFunction = function () { };
  310. if (!ev) {
  311. this._callbacks = {};
  312. } else {
  313. calls = this._callbacks;
  314. if (calls) {
  315. if (!callback) {
  316. calls[ev] = [];
  317. } else {
  318. var list = calls[ev];
  319. if (!list) { return this; }
  320. for (i = 0, l = list.length; i < l; i++) {
  321. if (callback === list[i]) {
  322. list[i] = emptyFunction;
  323. break;
  324. }
  325. }
  326. }
  327. }
  328. }
  329. return this;
  330. };
  331. model.ribsUI.set({ owner: model });
  332. model.ribsUI.bind("all", function (event) {
  333. var ev = "ribsUI:" + event;
  334. model.trigger(ev, Array.prototype.slice.call(arguments, 1));
  335. });
  336. }
  337. };
  338. Ribs.log = function (msg) {
  339. if (typeof(console) !== "undefined") {
  340. console.log(msg);
  341. }
  342. };
  343. // Default mixin classes
  344. Ribs.mixins.invalidateOnChange = function (classOptions) {
  345. var InvalidateOnChangeInst = function (parent) {
  346. return _.extend({
  347. excludedAttributes: null,
  348. includedAttributes: null,
  349. excludedRibsUIAttributes: null,
  350. includedRibsUIAttributes: null,
  351. modelChanging: function () {
  352. if (this.dataModel) {
  353. this.dataModel.unbind("change", this.change);
  354. }
  355. if (this.uiModel.safeUnbind) {
  356. this.uiModel.safeUnbind("change", this.ribsUIChange);
  357. }
  358. },
  359. modelChanged: function () {
  360. if (this.dataModel) {
  361. this.dataModel.bind("change", this.change);
  362. }
  363. this.uiModel.bind("change", this.ribsUIChange);
  364. },
  365. change: function (ev) {
  366. _.each(ev.changedAttributes(), this.checkAttribute);
  367. },
  368. checkAttribute: function (value, attrName) {
  369. var excluded = this.excludedAttributes && _.indexOf(this.excludedAttributes, attrName) !== -1,
  370. included = this.includedAttributes && _.indexOf(this.includedAttributes, attrName) !== -1;
  371. if (!excluded && included && !parent.invalidated) {
  372. parent.invalidated = true;
  373. _.defer(parent.render);
  374. }
  375. },
  376. ribsUIChange: function (ev) {
  377. _.each(ev.changedAttributes(), this.checkUIAttribute);
  378. },
  379. checkUIAttribute: function (value, attrName) {
  380. var excluded = this.excludedRibsUIAttributes && _.indexOf(this.excludedRibsUIAttributes, attrName) !== -1,
  381. included = this.includedRibsUIAttributes && _.indexOf(this.includedRibsUIAttributes, attrName) !== -1;
  382. if (!excluded && included && !parent.invalidated) {
  383. parent.invalidated = true;
  384. _.defer(parent.render);
  385. }
  386. }
  387. }, classOptions || {});
  388. };
  389. return InvalidateOnChangeInst;
  390. };
  391. Ribs.mixins.simpleList = function (classOptions) {
  392. classOptions = classOptions || {};
  393. var ItemRenderer = classOptions.ItemRenderer,
  394. SimpleListInst = function (parent) {
  395. var listModel, listViews, refreshingList;
  396. return _.extend({
  397. itemTagName: null,
  398. itemClassName: null,
  399. modelChanging: function () {
  400. _.each(listViews, function (view) {
  401. view.dispose();
  402. });
  403. listViews = {};
  404. if (listModel) {
  405. listModel.unbind("add", this.addOne);
  406. listModel.unbind("remove", this.removeOne);
  407. listModel.unbind("refresh", this.addAll);
  408. }
  409. },
  410. modelChanged: function () {
  411. listModel = this.myValue ? this.myValue : this.dataModel;
  412. if (listModel) {
  413. listModel.bind("add", this.addOne);
  414. listModel.bind("remove", this.removeOne);
  415. listModel.bind("refresh", this.addAll);
  416. this.addAll();
  417. }
  418. },
  419. redraw: function () {
  420. this.el.children().detach();
  421. _.each(listViews, _.bind(function (view) {
  422. this.el.append(view.el);
  423. }, this));
  424. },
  425. render: function () {
  426. _.each(listViews, function (view) {
  427. view.render();
  428. });
  429. },
  430. addOne: function (item) {
  431. if (!listViews.hasOwnProperty(item.cid)) {
  432. var listView = new ItemRenderer({
  433. model: item,
  434. tagName: this.itemTagName,
  435. className: this.itemClassName
  436. });
  437. listViews[item.cid] = listView;
  438. if (!refreshingList) {
  439. parent.invalidated = true;
  440. parent.render();
  441. }
  442. }
  443. },
  444. addAll: function () {
  445. refreshingList = true;
  446. if (listModel.each) {
  447. listModel.each(this.addOne);
  448. }
  449. parent.invalidated = true;
  450. parent.render();
  451. refreshingList = false;
  452. },
  453. removeOne: function (item) {
  454. delete listViews[item.cid];
  455. $(item.el).remove();
  456. }
  457. }, classOptions || {});
  458. };
  459. return SimpleListInst;
  460. };
  461. Ribs.mixins.templated = function (classOptions) {
  462. classOptions = classOptions || {};
  463. var defaultTemplateFunction = classOptions.templateSelector && _.template($(classOptions.templateSelector)),
  464. TemplatedInst = function () {
  465. return _.extend({
  466. templateSelector: null,
  467. templateFunction: defaultTemplateFunction,
  468. className: null,
  469. redraw: function () {
  470. var modelJSON = this.dataModel ? this.dataModel.toJSON() : {},
  471. uiModelJSON = this.uiModel.toJSON(),
  472. json = _.extend(modelJSON, uiModelJSON);
  473. json.t = function (name) {
  474. return this.hasOwnProperty(name) ? this[name] : "";
  475. };
  476. this.el.html(this.templateFunction(json));
  477. if (this.className) {
  478. this.el.toggleClass(this.className, true);
  479. }
  480. }
  481. }, classOptions);
  482. };
  483. return TemplatedInst;
  484. };
  485. Ribs.mixins.editable = function (classOptions) {
  486. var EditableInst = Ribs.mixins.mixinComposite(_.extend({
  487. mixinClasses: [
  488. Ribs.mixins.toggleAttribute({
  489. onEvent: "click",
  490. offEvent: "click",
  491. uiAttributeName: "editing"
  492. }),
  493. Ribs.mixins.toggleableClass({
  494. className: "editing",
  495. uiAttributeName: "editing"
  496. })
  497. ]
  498. }, classOptions || {}));
  499. return EditableInst;
  500. };
  501. Ribs.mixins.editableText = function (classOptions) {
  502. classOptions = classOptions || {};
  503. var EditableTextInst = Ribs.mixins.mixinComposite(_.extend({
  504. mixins: [
  505. { mixinComposite: {
  506. elementCreator: "<span>moi</span>",
  507. mixins: [
  508. { toggleableElement: { uiAttributeName: "editing", inverse: true } }
  509. ]
  510. }},
  511. { mixinComposite: {
  512. elementCreator: "<input type=\"text\" />",
  513. mixins: [
  514. { toggleableElement: { uiAttributeName: "editing" } },
  515. { textValueEdit: {
  516. attributeName: classOptions.attributeName,
  517. uiAttributeName: classOptions.uiAttributeName
  518. }}
  519. ]
  520. }}
  521. ]
  522. }, classOptions || {}));
  523. return EditableTextInst;
  524. };
  525. Ribs.mixins.hoverable = function (classOptions) {
  526. var HoverableInst = Ribs.mixins.mixinComposite(_.extend({
  527. mixinClasses: [
  528. Ribs.mixins.toggleAttribute({
  529. onEvent: "mouseenter",
  530. offEvent: "mouseleave",
  531. uiAttributeName: "hovering"
  532. }),
  533. Ribs.mixins.toggleableClass({
  534. className: "hovering",
  535. uiAttributeName: "hovering"
  536. })
  537. ]
  538. }, classOptions || {}));
  539. return HoverableInst;
  540. };
  541. Ribs.mixins.openable = function (classOptions) {
  542. var OpenableInst = Ribs.mixins.mixinComposite(_.extend({
  543. mixinClasses: [
  544. Ribs.mixins.toggleAttribute({
  545. onEvent: "click",
  546. offEvent: "click",
  547. uiAttributeName: "open"
  548. }),
  549. Ribs.mixins.toggleableClass({
  550. className: "open",
  551. uiAttributeName: "open"
  552. })
  553. ]
  554. }, classOptions || {}));
  555. return OpenableInst;
  556. };
  557. Ribs.mixins.selectable = function (classOptions) {
  558. var SelectableInst = Ribs.mixins.mixinComposite(_.extend({
  559. mixinClasses: [
  560. Ribs.mixins.toggleAttribute({
  561. onEvent: "click",
  562. offEvent: "click",
  563. uiAttributeName: "selected"
  564. }),
  565. Ribs.mixins.toggleableClass({
  566. className: "selected",
  567. uiAttributeName: "selected"
  568. })
  569. ]
  570. }, classOptions || {}));
  571. return SelectableInst;
  572. };
  573. Ribs.mixins.functionValueEdit = function (classOptions) {
  574. var FunctionValueEditInst = Ribs.mixins.textValueEdit(_.extend({
  575. readFunctionName: null,
  576. writeFunctionName: null,
  577. readFunction: function (value) {
  578. return (value && value[this.readFunctionName]) ? value[this.readFunctionName]() : value;
  579. },
  580. writeFunction: function (value, oldValue) {
  581. if (oldValue && oldValue[this.writeFunctionName]) {
  582. oldValue[this.writeFunctionName](value);
  583. return oldValue;
  584. }
  585. return value;
  586. }
  587. }, classOptions || {}));
  588. return FunctionValueEditInst;
  589. };
  590. Ribs.mixins.selectEdit = function (classOptions) {
  591. classOptions = classOptions || {};
  592. var SelectEditInst = function () {
  593. return _.extend({
  594. elementSelector: classOptions.attributeName && '[name|="' + classOptions.attributeName + '"]',
  595. selectOptions: [],
  596. modelChanging: function () {
  597. this.uiModel.unbind("commitEdit", this.commit);
  598. this.uiModel.unbind("cancelEdit", this.redraw);
  599. },
  600. modelChanged: function () {
  601. this.uiModel.bind("commitEdit", this.commit);
  602. this.uiModel.bind("cancelEdit", this.redraw);
  603. },
  604. redraw: function () {
  605. if (this.el.is("select")) {
  606. this.selectEl = this.el;
  607. } else {
  608. if (this.selectEl) { this.selectEl.remove(); }
  609. this.selectEl = $("<select></select>");
  610. this.el.append(this.selectEl);
  611. }
  612. if (this.dataModel) {
  613. var val = this.dataModel.get(this.attributeName);
  614. _.each(this.selectOptions, _.bind(function (option) {
  615. var optionEl = $('<option></option>');
  616. optionEl
  617. .attr("value", option.value)
  618. .text(option.text);
  619. if (option.value === val) {
  620. optionEl.attr("selected", "selected");
  621. }
  622. this.selectEl.append(optionEl);
  623. }, this));
  624. }
  625. },
  626. commit: function () {
  627. if (this.selectEl) {
  628. var value = this.selectEl.val(), values = {};
  629. values[this.attributeName] = value;
  630. this.dataModel.set(values);
  631. }
  632. }
  633. }, classOptions || {});
  634. };
  635. return SelectEditInst;
  636. };
  637. Ribs.mixins.textValueEdit = function (classOptions) {
  638. classOptions = classOptions || {};
  639. var TextValueEditInst = function () {
  640. return _.extend({
  641. elementSelector: classOptions.attributeName && '[name|="' + classOptions.attributeName + '"]',
  642. readFunction: null,
  643. writeFunction: null,
  644. modelChanging: function () {
  645. this.uiModel.unbind("commitEdit", this.commit);
  646. this.uiModel.unbind("cancelEdit", this.redraw);
  647. },
  648. modelChanged: function () {
  649. this.uiModel.bind("commitEdit", this.commit);
  650. this.uiModel.bind("cancelEdit", this.redraw);
  651. },
  652. redraw: function () {
  653. var value = this.myValue;
  654. if (this.readFunction) {
  655. value = this.readFunction(value);
  656. }
  657. this.el.val(value);
  658. },
  659. commit: function () {
  660. if (this.el) {
  661. var value = this.el.val(), values = {};
  662. if (this.writeFunction) {
  663. value = this.writeFunction(value, this.myValue);
  664. }
  665. if (this.attributeName) {
  666. values[this.attributeName] = value;
  667. this.dataModel.set(values);
  668. } else if (this.uiAttributeName) {
  669. values[this.uiAttributeName] = value;
  670. this.uiModel.set(values);
  671. }
  672. }
  673. }
  674. }, classOptions || {});
  675. };
  676. return TextValueEditInst;
  677. };
  678. Ribs.mixins.cancelEdit = function (classOptions) {
  679. var CancelEditInst = function () {
  680. return _.extend({
  681. events: {
  682. "click": "cancel"
  683. },
  684. cancel: function () {
  685. this.uiModel.trigger("cancelEdit");
  686. this.uiModel.set({ editing: false });
  687. }
  688. }, classOptions || {});
  689. };
  690. return CancelEditInst;
  691. };
  692. Ribs.mixins.commitEdit = function (classOptions) {
  693. var CommitEditInst = function () {
  694. return _.extend({
  695. events: {
  696. "click": "commit"
  697. },
  698. commit: function () {
  699. this.uiModel.trigger("commitEdit");
  700. this.uiModel.set({ editing: false });
  701. }
  702. }, classOptions || {});
  703. };
  704. return CommitEditInst;
  705. };
  706. Ribs.mixins.toggleAttribute = function (classOptions) {
  707. classOptions = classOptions || {};
  708. var ToggleAttributeInst = function () {
  709. return _.extend({
  710. events: {},
  711. attributeDefaultValue: false,
  712. onEvent: "click",
  713. offEvent: null,
  714. bindEvents: function () {
  715. if (this.onEvent) {
  716. this.events[this.onEvent] = "toggleOn";
  717. }
  718. if (this.offEvent && this.offEvent !== this.onEvent) {
  719. this.events[this.offEvent] = "toggleOff";
  720. }
  721. },
  722. updateValue: function (newValue) {
  723. var values = {};
  724. if (this.attributeName && this.dataModel) {
  725. values[this.attributeName] = newValue;
  726. this.dataModel.set(values);
  727. } else if (this.uiAttributeName) {
  728. values[this.uiAttributeName] = newValue;
  729. this.uiModel.set(values);
  730. }
  731. },
  732. modelChanged: function (model) {
  733. if (typeof(this.myValue) === "undefined") {
  734. this.updateValue(this.attributeDefaultValue);
  735. }
  736. },
  737. toggleOn: function () {
  738. var newValue = (this.onEvent === this.offEvent) ? !this.myValue : true;
  739. this.updateValue(newValue);
  740. },
  741. toggleOff: function () {
  742. if (this.onEvent !== this.offEvent) {
  743. this.updateValue(false);
  744. }
  745. }
  746. }, classOptions || {});
  747. };
  748. return ToggleAttributeInst;
  749. };
  750. Ribs.mixins.everyOtherChild = function (classOptions) {
  751. var EveryOtherChildInst = function () {
  752. return _.extend({
  753. childClassName: null,
  754. refresh: function () {
  755. if (!this.childClassName) {
  756. return;
  757. }
  758. var odd = false;
  759. this.el.children().each(function (index, child) {
  760. $(child).toggleClass(this.childClassName, odd);
  761. odd = !odd;
  762. });
  763. }
  764. }, classOptions || {});
  765. };
  766. return EveryOtherChildInst;
  767. };
  768. Ribs.mixins.toggleableClass = function (classOptions) {
  769. classOptions = classOptions || {};
  770. var ToggleableClassInst = function () {
  771. return _.extend({
  772. className: classOptions.attributeName || classOptions.uiAttributeName,
  773. inverse: false,
  774. refresh: function () {
  775. var value = this.myValue;
  776. if (this.inverse) {
  777. value = !value;
  778. }
  779. if (this.el.hasClass(this.className) !== value) {
  780. this.el.toggleClass(this.className, value);
  781. }
  782. }
  783. }, classOptions || {});
  784. };
  785. return ToggleableClassInst;
  786. };
  787. Ribs.mixins.toggleableElement = function (classOptions) {
  788. var ToggleableElementInst = function (parent) {
  789. var uiEventName;
  790. return _.extend({
  791. uiAttributeName: "open",
  792. inverse: false,
  793. modelChanging: function () {
  794. if (this.uiModel && uiEventName) {
  795. this.uiModel.unbind(uiEventName, this.attributeChanged);
  796. }
  797. },
  798. modelChanged: function () {
  799. if (this.uiModel) {
  800. uiEventName = "change:" + this.uiAttributeName;
  801. this.uiModel.bind(uiEventName, this.attributeChanged);
  802. }
  803. },
  804. redraw: function () {
  805. var value = this.myValue;
  806. if (this.inverse) {
  807. value = !value;
  808. }
  809. this.el.toggle(value);
  810. },
  811. attributeChanged: function () {
  812. parent.invalidated = true;
  813. }
  814. }, classOptions || {});
  815. };
  816. return ToggleableElementInst;
  817. };